Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f8d9229

Browse files
author
sunlei
committed
refactor openApi parse multi spec
1 parent 88f35ac commit f8d9229

File tree

6 files changed

+136
-58
lines changed

6 files changed

+136
-58
lines changed

server/node-service/src/plugins/cloudinary/index.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ import { readYaml } from "../../common/util";
22
import _ from "lodash";
33
import path from "path";
44
import { OpenAPI } from "openapi-types";
5-
import { ConfigToType, DataSourcePlugin } from "openblocks-sdk/dataSource";
5+
import { ConfigToType, DataSourcePlugin, QueryConfig } from "openblocks-sdk/dataSource";
66
import { runOpenApi } from "../openApi";
7-
import { parseOpenApi, ParseOpenApiOptions } from "../openApi/parse";
7+
import { parseMultiOpenApi, parseOpenApi, ParseOpenApiOptions } from "../openApi/parse";
88
import { mergeCategories } from "../../plugins/openApi/util";
99

1010
// all OpenAPI specs generated from https://www.postman.com/cloudinaryteam/workspace/programmable-media/collection/16080251-d28221d4-b2f8-4244-a4eb-7e77abe3a857?ctx=documentation
1111
const adminApiSpec = readYaml(path.join(__dirname, "./adminApi.spec.yaml"));
1212
const uploadApiSpec = readYaml(path.join(__dirname, "./uploadApi.spec.yaml"));
13+
const specList = [
14+
{ spec: adminApiSpec, id: "admin" },
15+
{ spec: uploadApiSpec, id: "upload" },
16+
];
1317

1418
const dataSourceConfig = {
1519
type: "dataSource",
@@ -42,34 +46,36 @@ const parseOptions: ParseOpenApiOptions = {
4246

4347
type DataSourceConfigType = ConfigToType<typeof dataSourceConfig>;
4448

49+
let queryConfig: QueryConfig;
50+
4551
const cloudinaryPlugin: DataSourcePlugin<any, DataSourceConfigType> = {
4652
id: "cloudinary",
4753
name: "cloudinary",
4854
icon: "cloudinary.svg",
4955
category: "api",
5056
dataSourceConfig,
5157
queryConfig: async () => {
52-
const [parsedAdminApi, parsedUploadApi] = await Promise.all([
53-
parseOpenApi(adminApiSpec, parseOptions),
54-
parseOpenApi(uploadApiSpec, parseOptions),
55-
]);
56-
return {
57-
type: "query",
58-
label: "Action",
59-
categories: {
60-
label: "Resources",
61-
items: mergeCategories(parsedUploadApi.categories, parsedAdminApi.categories),
62-
},
63-
actions: parsedAdminApi.actions.concat(parsedUploadApi.actions),
64-
};
58+
if (!queryConfig) {
59+
const { actions, categories } = await parseMultiOpenApi(specList, parseOptions);
60+
queryConfig = {
61+
type: "query",
62+
label: "Action",
63+
categories: {
64+
label: "Resources",
65+
items: categories,
66+
},
67+
actions,
68+
};
69+
}
70+
return queryConfig;
6571
},
6672
run: function (actionData, dataSourceConfig): Promise<any> {
6773
const runApiDsConfig = {
6874
url: "",
6975
serverURL: "",
7076
dynamicParamsConfig: dataSourceConfig,
7177
};
72-
return runOpenApi(actionData, runApiDsConfig, [adminApiSpec, uploadApiSpec]);
78+
return runOpenApi(actionData, runApiDsConfig, specList);
7379
},
7480
};
7581

server/node-service/src/plugins/did/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ import path from "path";
44
import { OpenAPIV3, OpenAPI } from "openapi-types";
55
import { QueryConfig, ConfigToType, DataSourcePlugin } from "openblocks-sdk/dataSource";
66
import { runOpenApi } from "../openApi";
7-
import { parseMultiOpenApi, parseOpenApi, ParseOpenApiOptions } from "../openApi/parse";
7+
import { MultiOpenApiSpecItem, parseMultiOpenApi, ParseOpenApiOptions } from "../openApi/parse";
88
import { appendTags } from "../../plugins/openApi/util";
99
import { readdirSync } from "fs";
1010

11-
const specList: OpenAPI.Document[] = [];
11+
const specList: MultiOpenApiSpecItem[] = [];
1212
const specFiles = readdirSync(path.join(__dirname, "./did.spec"));
1313
const start = performance.now();
1414
specFiles.forEach((specFile) => {
1515
const spec = readYaml(path.join(__dirname, "./did.spec", specFile));
16-
appendTags(spec, _.upperFirst(specFile.replace(".json", "")));
17-
specList.push(spec);
16+
const tag = _.upperFirst(specFile.replace(".json", ""));
17+
appendTags(spec, tag);
18+
specList.push({
19+
spec,
20+
id: tag,
21+
});
1822
});
1923
logger.info("did spec list loaded, duration: %d ms", performance.now() - start);
2024

server/node-service/src/plugins/openApi/index.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import SwaggerClient from "swagger-client";
22
import SwaggerParser from "@apidevtools/swagger-parser";
33
import { ConfigToType, DataSourcePlugin } from "openblocks-sdk/dataSource";
4-
import { authParamsConfig, parseOpenApi, retrieveSpec } from "./parse";
4+
import { authParamsConfig, MultiOpenApiSpecItem, parseOpenApi, retrieveSpec } from "./parse";
55
import {
66
extractSecurityParams,
77
findOperation,
@@ -53,21 +53,32 @@ interface ActionDataType {
5353
export async function runOpenApi(
5454
actionData: ActionDataType,
5555
dataSourceConfig: DataSourceDataType,
56-
spec: OpenAPI.Document | OpenAPI.Document[],
56+
spec: OpenAPI.Document | MultiOpenApiSpecItem[],
5757
defaultHeaders?: Record<string, string>
5858
) {
59-
const specList = Array.isArray(spec) ? spec : [spec];
60-
const definitions = await Promise.all(specList.map((i) => SwaggerParser.dereference(i)));
59+
const specList = Array.isArray(spec) ? spec : [{ spec, id: "" }];
60+
const definitions = await Promise.all(
61+
specList.map(async ({ id, spec }) => {
62+
const deRefedSpec = await SwaggerParser.dereference(spec);
63+
return {
64+
def: deRefedSpec,
65+
id,
66+
};
67+
})
68+
);
6169
const { actionName, ...otherActionData } = actionData;
6270
const { serverURL } = dataSourceConfig;
6371

6472
let definition: OpenAPI.Document | undefined;
6573
let operation;
74+
let realOperationId;
6675

67-
for (const def of definitions) {
68-
operation = findOperation(actionName, def);
69-
if (operation) {
76+
for (const { id, def } of definitions) {
77+
const ret = findOperation(actionName, def, id);
78+
if (ret) {
7079
definition = def;
80+
operation = ret.operation;
81+
realOperationId = ret.realOperationId;
7182
break;
7283
}
7384
}
@@ -94,7 +105,7 @@ export async function runOpenApi(
94105
const securities = extractSecurityParams(dataSourceConfig.dynamicParamsConfig, definition);
95106
const response = await SwaggerClient.execute({
96107
spec: definition,
97-
operationId: actionName,
108+
operationId: realOperationId,
98109
parameters,
99110
requestBody,
100111
securities,

server/node-service/src/plugins/openApi/parse.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import _ from "lodash";
1414
import SwaggerClient from "swagger-client";
1515
import {
1616
appendCategories,
17+
getOperationId,
1718
isOas3HttpMethods,
1819
isOas3RefObject,
1920
isSwagger2HttpMethods,
@@ -175,11 +176,18 @@ interface OpenAPIParseResult {
175176
categories: ActionCategory[];
176177
}
177178

179+
export type MultiOpenApiSpecItem = {
180+
spec: OpenAPI.Document | string;
181+
id: string;
182+
};
183+
178184
export async function parseMultiOpenApi(
179-
spec: (OpenAPI.Document | string)[],
180-
options?: ParseOpenApiOptions
185+
specList: MultiOpenApiSpecItem[],
186+
options?: Omit<ParseOpenApiOptions, "specId">
181187
): Promise<OpenAPIParseResult> {
182-
const results = await Promise.all(spec.map((i) => parseOpenApi(i, options)));
188+
const results = await Promise.all(
189+
specList.map(({ id, spec }) => parseOpenApi(spec, options, id))
190+
);
183191
const reducedResults = results.reduce(
184192
(a, b) => {
185193
return {
@@ -194,7 +202,12 @@ export async function parseMultiOpenApi(
194202

195203
export async function parseOpenApi(
196204
specJsonOrObj: OpenAPI.Document | string,
197-
options?: ParseOpenApiOptions
205+
options?: ParseOpenApiOptions,
206+
/**
207+
* when parse multi open api this field is required.
208+
* This will be used to ensure operationId is unique among all operations in all specs.
209+
*/
210+
specId?: string
198211
): Promise<OpenAPIParseResult> {
199212
let spec = specJsonOrObj;
200213
if (typeof specJsonOrObj === "string") {
@@ -245,7 +258,7 @@ export async function parseOpenApi(
245258
return;
246259
}
247260

248-
const operationId: string = SwaggerClient.helpers.opId(operation, path, httpMethod);
261+
const operationId: string = getOperationId(operation, path, httpMethod, specId);
249262
if (!operationId) {
250263
console.warn("can not get operationId:", operation);
251264
return;

server/node-service/src/plugins/openApi/util.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { File } from "formdata-node";
44
import { OpenAPI, OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from "openapi-types";
55
import swaggerClient from "swagger-client";
66
import { ActionCategory } from "openblocks-sdk/dataSource";
7+
import SwaggerClient from "swagger-client";
78

89
export const MediaTypeOctetStream = "application/octet-stream";
910
export const MediaTypeUrlEncoded = "application/x-www-form-urlencoded";
@@ -186,7 +187,7 @@ export function findOas3FilePropertiesFromSchema(
186187
});
187188
}
188189

189-
export function findOperation(id: string, spec: OpenAPI.Document) {
190+
export function findOperation(id: string, spec: OpenAPI.Document, specId: string = "") {
190191
if (!spec.paths) {
191192
return null;
192193
}
@@ -197,8 +198,11 @@ export function findOperation(id: string, spec: OpenAPI.Document) {
197198
}
198199
for (const method of Object.keys(pathObj)) {
199200
const operation: OpenAPI.Operation = (pathObj as any)[method];
200-
if (swaggerClient.helpers.opId(operation, path, method) === id) {
201-
return operation;
201+
if (getOperationId(operation, path, method, specId) === id) {
202+
return {
203+
operation,
204+
realOperationId: getOperationId(operation, path, method),
205+
};
202206
}
203207
}
204208
}
@@ -345,13 +349,13 @@ export function getSchemaType(
345349
return schema.type;
346350
}
347351

348-
export function getSchemaExample(
349-
schema?:
350-
| OpenAPIV2.SchemaObject
351-
| OpenAPIV2.ReferenceObject
352-
| OpenAPIV3.SchemaObject
353-
| OpenAPIV3.ReferenceObject
354-
): any {
352+
type SchemaObject =
353+
| OpenAPIV2.SchemaObject
354+
| OpenAPIV2.ReferenceObject
355+
| OpenAPIV3.SchemaObject
356+
| OpenAPIV3.ReferenceObject;
357+
358+
export function getSchemaExample(schema?: SchemaObject): any {
355359
if (!schema || isOas3RefObject(schema) || isSwagger2RefObject(schema)) {
356360
return;
357361
}
@@ -376,6 +380,22 @@ export function getSchemaExample(
376380
);
377381
return _.isNil(itemExample) ? [] : [itemExample];
378382
}
383+
if (schema.oneOf && schema.oneOf.length > 0) {
384+
const firstSchema = schema.oneOf[0];
385+
return getSchemaExample(firstSchema as SchemaObject);
386+
}
387+
if (schema.anyOf && schema.anyOf.length > 0) {
388+
const firstSchema = schema.anyOf[0];
389+
return getSchemaExample(firstSchema as SchemaObject);
390+
}
391+
if (schema.allOf && schema.allOf.length > 0) {
392+
return (schema.allOf as SchemaObject[]).reduce((a: any, b: SchemaObject) => {
393+
return {
394+
...a,
395+
...getSchemaExample(b),
396+
};
397+
}, {});
398+
}
379399
let ret: any;
380400
if (schema.properties) {
381401
Object.entries(schema.properties).forEach(([name, def]) => {
@@ -419,3 +439,16 @@ export function appendCategories(a: ActionCategory[], b: ActionCategory[]) {
419439
a.push(i);
420440
});
421441
}
442+
443+
export function getOperationId(
444+
operation: OpenAPI.Operation,
445+
path: string,
446+
method: string,
447+
specId: string = ""
448+
) {
449+
const operationId: string = SwaggerClient.helpers.opId(operation, path, method);
450+
if (!operationId) {
451+
return "";
452+
}
453+
return operationId + specId;
454+
}

server/node-service/src/plugins/twilio/index.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import _ from "lodash";
33
import fs from "fs";
44
import path from "path";
55
import { OpenAPI } from "openapi-types";
6-
import { ConfigToType, DataSourcePlugin } from "openblocks-sdk/dataSource";
6+
import { ConfigToType, DataSourcePlugin, QueryConfig } from "openblocks-sdk/dataSource";
77
import { runOpenApi } from "../openApi";
8-
import { parseMultiOpenApi, ParseOpenApiOptions } from "../openApi/parse";
8+
import { MultiOpenApiSpecItem, parseMultiOpenApi, ParseOpenApiOptions } from "../openApi/parse";
99
import { appendTags } from "../openApi/util";
1010

1111
function genTagFromFileName(name: string) {
@@ -19,14 +19,18 @@ function genTagFromFileName(name: string) {
1919
}, "");
2020
}
2121

22-
const specList: OpenAPI.Document[] = [];
22+
const specList: MultiOpenApiSpecItem[] = [];
2323

2424
const start = performance.now();
2525
const specFiles = fs.readdirSync(path.join(__dirname, "./twilio.spec"));
2626
specFiles.forEach((specFile) => {
2727
const spec = readYaml(path.join(__dirname, "./twilio.spec", specFile));
28-
appendTags(spec, genTagFromFileName(specFile));
29-
specList.push(spec);
28+
const tag = genTagFromFileName(specFile);
29+
appendTags(spec, tag);
30+
specList.push({
31+
id: tag,
32+
spec,
33+
});
3034
});
3135
logger.info("twilio spec list loaded, duration: %d ms", performance.now() - start);
3236

@@ -54,6 +58,8 @@ const parseOptions: ParseOpenApiOptions = {
5458
},
5559
};
5660

61+
let queryConfig: QueryConfig;
62+
5763
type DataSourceConfigType = ConfigToType<typeof dataSourceConfig>;
5864

5965
const twilioPlugin: DataSourcePlugin<any, DataSourceConfigType> = {
@@ -62,18 +68,23 @@ const twilioPlugin: DataSourcePlugin<any, DataSourceConfigType> = {
6268
icon: "twilio.svg",
6369
category: "api",
6470
dataSourceConfig,
71+
6572
queryConfig: async () => {
66-
const { actions, categories } = await parseMultiOpenApi(specList, parseOptions);
67-
return {
68-
type: "query",
69-
label: "Action",
70-
categories: {
71-
label: "Resources",
72-
items: categories,
73-
},
74-
actions,
75-
};
73+
if (!queryConfig) {
74+
const { actions, categories } = await parseMultiOpenApi(specList, parseOptions);
75+
queryConfig = {
76+
type: "query",
77+
label: "Action",
78+
categories: {
79+
label: "Resources",
80+
items: categories,
81+
},
82+
actions,
83+
};
84+
}
85+
return queryConfig;
7686
},
87+
7788
run: function (actionData, dataSourceConfig): Promise<any> {
7889
const runApiDsConfig = {
7990
url: "",

0 commit comments

Comments
 (0)