UNPKG

6.65 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const path_1 = __importDefault(require("path"));
7const typescript_1 = __importDefault(require("typescript"));
8const openapi_template_1 = require("./openapi-template");
9const convert_type_1 = require("./convert-type");
10exports.INTENT_ACTION = 'action';
11/**
12 * find action method in class and return relevant node
13 */
14function findActionMethod(node) {
15 const action = node.members.find((node) => {
16 if (node.name) {
17 return node.name.getText() === exports.INTENT_ACTION;
18 }
19 return false;
20 });
21 if (typescript_1.default.isPropertyDeclaration(action)) {
22 return action.initializer;
23 }
24 return action;
25}
26function serializeBody(method, checker) {
27 const symbol = checker.getSymbolAtLocation(method.name);
28 let data;
29 let error;
30 if (symbol) {
31 const returnTypeNode = method.type;
32 let dataIndex = 0; // index of ReturnedData in TFetchPromise<ReturnedData, ReturnedError>
33 let errorIndex = 1; // index of ReturnedError in TFetchPromise<ReturnedData, ReturnedError>
34 const typeName = checker.typeToString(checker.getTypeFromTypeNode(returnTypeNode));
35 if (typeName.startsWith('Promise<TSaveStatePayload')) {
36 dataIndex = 1; // index of ReturnedData in TSavePromise<State, ReturnedData, ReturnedError>
37 errorIndex = 2; // index of ReturnedData in TSavePromise<State, ReturnedData, ReturnedError>
38 }
39 if (returnTypeNode && typescript_1.default.isTypeReferenceNode(returnTypeNode)) {
40 const typeData = checker.getTypeFromTypeNode(returnTypeNode.typeArguments[dataIndex]);
41 data = convert_type_1.convertType(typeData, checker);
42 const errorArgument = returnTypeNode.typeArguments[errorIndex];
43 if (errorArgument) {
44 const typeError = checker.getTypeFromTypeNode(errorArgument);
45 error = convert_type_1.convertType(typeError, checker);
46 }
47 }
48 }
49 return { data, error };
50}
51function resolveParameterTypeIndex(t, checker, i, j) {
52 let index = i;
53 if (checker.typeToString(t).startsWith('TSaveActionEvent')) {
54 index = j;
55 }
56 return index;
57}
58/**
59 * Find the Parameter type in intent and convert it to json-schema
60 */
61function serializeParameters(sym, node, checker) {
62 if (sym) {
63 const typ = checker.getTypeOfSymbolAtLocation(sym, node);
64 if (typ.aliasTypeArguments) {
65 const index = resolveParameterTypeIndex(typ, checker, 0, 1);
66 return convert_type_1.convertType(typ.aliasTypeArguments[index], checker);
67 }
68 }
69}
70/**
71 * Find the auth context type and return it's name
72 * @param sym action method symbol
73 * @param node action method symbol value declaration
74 * @param checker program type checker
75 */
76function getIntentAuthType(sym, node, checker) {
77 if (sym) {
78 const typ = checker.getTypeOfSymbolAtLocation(sym, node);
79 if (typ.aliasTypeArguments) {
80 const index = resolveParameterTypeIndex(typ, checker, 1, 2);
81 const authType = checker.typeToString(typ.aliasTypeArguments[index]);
82 if (/apiKey/.test(authType))
83 return 'APIKEY';
84 if (/username/.test(authType) && /password/.test(authType))
85 return 'BASIC';
86 if (/accessToken/.test(authType))
87 return 'OAUTH2';
88 if (/undefined/.test(authType))
89 return 'NONE';
90 return authType;
91 }
92 }
93 return;
94}
95/**
96 * Convert single intent types to json schema
97 * This function tries to find the relevant type definitions, Params and `action` method resposne type
98 * and converts the types to json-schema
99 * @param intentPath absolute path to intent
100 * @param options compiler options
101 */
102function intentTypesToSchemaConverter(intentPath, options = { target: typescript_1.default.ScriptTarget.ES5, module: typescript_1.default.ModuleKind.CommonJS }) {
103 const program = typescript_1.default.createProgram([intentPath], options);
104 const sourceFile = program.getSourceFile(intentPath);
105 const checker = program.getTypeChecker();
106 const output = {
107 requestBody: {},
108 response: {}
109 };
110 if (sourceFile) {
111 typescript_1.default.forEachChild(sourceFile, visit);
112 }
113 return output;
114 function visit(node) {
115 // Only consider exported nodes
116 if (!isNodeExported(node)) {
117 return;
118 }
119 if (typescript_1.default.isClassDeclaration(node) && node.name) {
120 const actionMethodNode = findActionMethod(node);
121 if (actionMethodNode) {
122 const parameterNode = actionMethodNode.parameters[0];
123 const sym = checker.getSymbolAtLocation(parameterNode.name);
124 output.requestBody = serializeParameters(sym, parameterNode, checker);
125 output.response = serializeBody(actionMethodNode, checker);
126 output.intentAuthType = getIntentAuthType(sym, parameterNode, checker);
127 }
128 }
129 }
130 function isNodeExported(node) {
131 return ((typescript_1.default.getCombinedModifierFlags(node) & typescript_1.default.ModifierFlags.Export) !== 0 ||
132 (!!node.parent && node.parent.kind === typescript_1.default.SyntaxKind.SourceFile));
133 }
134}
135exports.intentTypesToSchemaConverter = intentTypesToSchemaConverter;
136/**
137 * Generate full openapi spec from intents
138 * @param intentsDir absolute path to intents directory
139 * @param intents list of intent names
140 * @param integrationUuid integration unique identifier
141 * @param integrationName name of the integration
142 */
143function generator({ intentsDir, intents, integrationUuid, integrationName }) {
144 const doc = openapi_template_1.topOfSpec(integrationName);
145 const schemas = intents.sort().reduce((acc, intent) => {
146 const intentPath = path_1.default.join(intentsDir, `${intent}.ts`);
147 const typeSchema = intentTypesToSchemaConverter(intentPath);
148 return Object.assign(acc, openapi_template_1.specPath({
149 integrationUuid,
150 intentName: intent,
151 response: { type: 'object', properties: typeSchema.response },
152 requestBody: typeSchema.requestBody,
153 oauth2: typeSchema.intentAuthType === 'OAUTH2'
154 }));
155 }, {});
156 return JSON.stringify(Object.assign(doc, { paths: schemas }), null, 2);
157}
158exports.default = generator;