swagger-typescript-api 13.0.0-experimental-1 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +22 -12
  3. package/cli/constants.js +3 -3
  4. package/cli/execute.js +52 -31
  5. package/cli/index.d.ts +1 -2
  6. package/cli/index.js +18 -17
  7. package/cli/operations/display-help.js +51 -29
  8. package/cli/parse-args.js +3 -3
  9. package/cli/process-option.js +28 -20
  10. package/index.d.ts +113 -8
  11. package/index.js +158 -135
  12. package/package.json +35 -30
  13. package/src/code-formatter.js +28 -13
  14. package/src/code-gen-process.js +357 -259
  15. package/src/commands/generate-templates/configuration.js +2 -2
  16. package/src/commands/generate-templates/index.js +1 -2
  17. package/src/commands/generate-templates/templates-gen-process.js +62 -35
  18. package/src/component-type-name-resolver.js +44 -0
  19. package/src/configuration.js +167 -95
  20. package/src/constants.js +28 -22
  21. package/src/index.js +3 -4
  22. package/src/schema-components-map.js +39 -23
  23. package/src/schema-parser/base-schema-parsers/array.js +43 -0
  24. package/src/schema-parser/base-schema-parsers/complex.js +51 -0
  25. package/src/schema-parser/base-schema-parsers/discriminator.js +301 -0
  26. package/src/schema-parser/base-schema-parsers/enum.js +158 -0
  27. package/src/schema-parser/base-schema-parsers/object.js +105 -0
  28. package/src/schema-parser/base-schema-parsers/primitive.js +63 -0
  29. package/src/schema-parser/complex-schema-parsers/all-of.js +26 -0
  30. package/src/schema-parser/complex-schema-parsers/any-of.js +34 -0
  31. package/src/schema-parser/complex-schema-parsers/not.js +9 -0
  32. package/src/schema-parser/complex-schema-parsers/one-of.js +27 -0
  33. package/src/schema-parser/mono-schema-parser.js +48 -0
  34. package/src/schema-parser/schema-formatters.js +69 -60
  35. package/src/schema-parser/schema-parser-fabric.js +131 -0
  36. package/src/schema-parser/schema-parser.js +208 -427
  37. package/src/schema-parser/schema-utils.js +123 -58
  38. package/src/schema-parser/util/enum-key-resolver.js +26 -0
  39. package/src/schema-routes/schema-routes.js +1225 -0
  40. package/src/schema-routes/util/specific-arg-name-resolver.js +26 -0
  41. package/src/schema-walker.js +93 -0
  42. package/src/swagger-schema-resolver.js +61 -28
  43. package/src/templates-worker.js +240 -0
  44. package/src/translators/javascript.js +83 -0
  45. package/src/translators/translator.js +35 -0
  46. package/src/type-name-formatter.js +33 -18
  47. package/src/util/file-system.js +30 -14
  48. package/src/util/id.js +2 -2
  49. package/src/util/internal-case.js +1 -1
  50. package/src/util/logger.js +46 -20
  51. package/src/util/name-resolver.js +52 -60
  52. package/src/util/object-assign.js +7 -3
  53. package/src/util/pascal-case.js +1 -1
  54. package/src/util/request.js +5 -5
  55. package/src/util/sort-by-property.js +17 -0
  56. package/templates/README.md +17 -17
  57. package/templates/base/README.md +7 -7
  58. package/templates/base/data-contract-jsdoc.ejs +37 -37
  59. package/templates/base/data-contracts.ejs +40 -27
  60. package/templates/base/enum-data-contract.ejs +12 -12
  61. package/templates/base/http-client.ejs +3 -3
  62. package/templates/base/http-clients/axios-http-client.ejs +139 -138
  63. package/templates/base/http-clients/fetch-http-client.ejs +224 -224
  64. package/templates/base/interface-data-contract.ejs +10 -10
  65. package/templates/base/object-field-jsdoc.ejs +28 -28
  66. package/templates/base/route-docs.ejs +30 -30
  67. package/templates/base/route-name.ejs +42 -42
  68. package/templates/base/route-type.ejs +22 -21
  69. package/templates/base/type-data-contract.ejs +15 -15
  70. package/templates/default/README.md +6 -6
  71. package/templates/default/api.ejs +69 -68
  72. package/templates/default/procedure-call.ejs +100 -100
  73. package/templates/default/route-types.ejs +32 -32
  74. package/templates/modular/README.md +6 -6
  75. package/templates/modular/api.ejs +28 -28
  76. package/templates/modular/procedure-call.ejs +100 -100
  77. package/templates/modular/route-types.ejs +18 -18
  78. package/src/schema-parser/schema-processor.js +0 -79
  79. package/src/schema-parser/schema-routes.js +0 -950
  80. package/src/templates.js +0 -182
  81. package/src/translators/JavaScript.js +0 -60
@@ -1,950 +0,0 @@
1
- const _ = require("lodash");
2
- const { generateId } = require("../util/id.js");
3
- const { SpecificArgNameResolver } = require("../util/name-resolver.js");
4
- const {
5
- DEFAULT_BODY_ARG_NAME,
6
- RESERVED_BODY_ARG_NAMES,
7
- RESERVED_HEADER_ARG_NAMES,
8
- RESERVED_PATH_ARG_NAMES,
9
- RESERVED_QUERY_ARG_NAMES,
10
- } = require("../constants.js");
11
- const { pascalCase } = require("../util/pascal-case");
12
-
13
- const CONTENT_KIND = {
14
- JSON: "JSON",
15
- URL_ENCODED: "URL_ENCODED",
16
- FORM_DATA: "FORM_DATA",
17
- IMAGE: "IMAGE",
18
- OTHER: "OTHER",
19
- TEXT: "TEXT",
20
- };
21
-
22
- class SchemaRoutes {
23
- /**
24
- * @type {CodeGenConfig}
25
- */
26
- config;
27
- /**
28
- * @type {SchemaProcessor}
29
- */
30
- schemaParser;
31
- /**
32
- * @type {SchemaUtils}
33
- */
34
- schemaUtils;
35
- /**
36
- * @type {TypeNameFormatter}
37
- */
38
- typeName;
39
- /**
40
- * @type {SchemaComponentsMap}
41
- */
42
- schemaComponentMap;
43
- /**
44
- * @type {Logger}
45
- */
46
- logger;
47
- /**
48
- * @type {Templates}
49
- */
50
- templates;
51
-
52
- FORM_DATA_TYPES = [];
53
-
54
- routes = [];
55
- hasSecurityRoutes = false;
56
- hasQueryRoutes = false;
57
- hasFormDataRoutes = false;
58
-
59
- constructor(config, schemaParser, schemaComponentMap, logger, templates, typeName) {
60
- this.config = config;
61
- this.schemaParser = schemaParser;
62
- this.schemaUtils = this.schemaParser.schemaUtils;
63
- this.typeName = typeName;
64
- this.schemaComponentMap = schemaComponentMap;
65
- this.logger = logger;
66
- this.templates = templates;
67
- }
68
-
69
- async init() {
70
- this.FORM_DATA_TYPES = _.uniq([
71
- await this.schemaUtils.getSchemaType({ type: "string", format: "file" }),
72
- await this.schemaUtils.getSchemaType({ type: "string", format: "binary" }),
73
- ]);
74
- }
75
-
76
- createRequestsMap = (routeInfoByMethodsMap) => {
77
- const parameters = _.get(routeInfoByMethodsMap, "parameters");
78
-
79
- return _.reduce(
80
- routeInfoByMethodsMap,
81
- (acc, requestInfo, method) => {
82
- if (_.startsWith(method, "x-") || ["parameters", "$ref"].includes(method)) {
83
- return acc;
84
- }
85
-
86
- acc[method] = {
87
- ...requestInfo,
88
- parameters: _.compact(_.concat(parameters, requestInfo.parameters)),
89
- };
90
-
91
- return acc;
92
- },
93
- {},
94
- );
95
- };
96
-
97
- parseRouteName = (originalRouteName) => {
98
- const routeName = this.config.hooks.onPreBuildRoutePath(originalRouteName) || originalRouteName;
99
-
100
- const pathParamMatches = (routeName || "").match(
101
- /({(([a-zA-Z]-?_?\.?){1,})([0-9]{1,})?})|(:(([a-zA-Z]-?_?\.?){1,})([0-9]{1,})?:?)/g,
102
- );
103
-
104
- // used in case when path parameters is not declared in requestInfo.parameters ("in": "path")
105
- const pathParams = _.reduce(
106
- pathParamMatches,
107
- (pathParams, match) => {
108
- const paramName = _.replace(match, /\{|\}|\:/g, "");
109
-
110
- if (!paramName) return pathParams;
111
-
112
- if (_.includes(paramName, "-")) {
113
- this.logger.warn("wrong path param name", paramName);
114
- }
115
-
116
- pathParams.push({
117
- $match: match,
118
- name: _.camelCase(paramName),
119
- required: true,
120
- type: "string",
121
- description: "",
122
- schema: {
123
- type: "string",
124
- },
125
- in: "path",
126
- });
127
-
128
- return pathParams;
129
- },
130
- [],
131
- );
132
-
133
- let fixedRoute = _.reduce(
134
- pathParams,
135
- (fixedRoute, pathParam, i, arr) => {
136
- const insertion = this.config.hooks.onInsertPathParam(pathParam.name, i, arr, fixedRoute) || pathParam.name;
137
- return _.replace(fixedRoute, pathParam.$match, `\${${insertion}}`);
138
- },
139
- routeName || "",
140
- );
141
-
142
- const queryParamMatches = fixedRoute.match(/(\{\?.*\})/g);
143
- const queryParams = [];
144
-
145
- if (queryParamMatches && queryParamMatches.length) {
146
- queryParamMatches.forEach((match) => {
147
- fixedRoute = fixedRoute.replace(match, "");
148
- });
149
-
150
- _.uniq(
151
- queryParamMatches
152
- .join(",")
153
- .replace(/(\{\?)|(\})|\s/g, "")
154
- .split(","),
155
- ).forEach((paramName) => {
156
- if (_.includes(paramName, "-")) {
157
- this.logger.warn("wrong query param name", paramName);
158
- }
159
-
160
- queryParams.push({
161
- $match: paramName,
162
- name: _.camelCase(paramName),
163
- required: true,
164
- type: "string",
165
- description: "",
166
- schema: {
167
- type: "string",
168
- },
169
- in: "query",
170
- });
171
- });
172
- }
173
-
174
- const result = {
175
- originalRoute: originalRouteName || "",
176
- route: fixedRoute,
177
- pathParams,
178
- queryParams,
179
- };
180
-
181
- return this.config.hooks.onBuildRoutePath(result) || result;
182
- };
183
-
184
- getRouteParams = (routeInfo, pathParamsFromRouteName, queryParamsFromRouteName) => {
185
- const { parameters } = routeInfo;
186
-
187
- const routeParams = {
188
- path: [],
189
- header: [],
190
- body: [],
191
- query: [],
192
- formData: [],
193
- cookie: [],
194
- };
195
-
196
- _.each(parameters, (parameter) => {
197
- const refTypeInfo = this.schemaParser.schemaUtils.getSchemaRefType(parameter);
198
- let routeParam = null;
199
-
200
- if (refTypeInfo && refTypeInfo.rawTypeData.in && refTypeInfo.rawTypeData) {
201
- if (!routeParams[refTypeInfo.rawTypeData.in]) {
202
- routeParams[refTypeInfo.rawTypeData.in] = [];
203
- }
204
-
205
- routeParam = {
206
- ...refTypeInfo.rawTypeData,
207
- ...(refTypeInfo.rawTypeData.schema || {}),
208
- };
209
- } else {
210
- if (!parameter.in) return;
211
-
212
- if (!routeParams[parameter.in]) {
213
- routeParams[parameter.in] = [];
214
- }
215
-
216
- routeParam = {
217
- ...parameter,
218
- ...(parameter.schema || {}),
219
- };
220
- }
221
-
222
- if (routeParam.in === "path") {
223
- if (!routeParam.name) return;
224
-
225
- routeParam.name = _.camelCase(routeParam.name);
226
- }
227
-
228
- if (routeParam) {
229
- routeParams[routeParam.in].push(routeParam);
230
- }
231
- });
232
-
233
- // used in case when path parameters is not declared in requestInfo.parameters ("in": "path")
234
- _.each(pathParamsFromRouteName, (pathParam) => {
235
- const alreadyExist = _.some(routeParams.path, (parameter) => parameter.name === pathParam.name);
236
-
237
- if (!alreadyExist) {
238
- routeParams.path.push(pathParam);
239
- }
240
- });
241
- // used in case when path parameters is not declared in requestInfo.parameters ("in": "path")
242
- _.each(queryParamsFromRouteName, (queryParam) => {
243
- const alreadyExist = _.some(routeParams.query, (parameter) => parameter.name === queryParam.name);
244
-
245
- if (!alreadyExist) {
246
- routeParams.query.push(queryParam);
247
- }
248
- });
249
-
250
- return routeParams;
251
- };
252
-
253
- getContentTypes = (requestInfo, extraContentTypes) =>
254
- _.uniq(
255
- _.compact([
256
- ...(extraContentTypes || []),
257
- ..._.flatten(_.map(requestInfo, (requestInfoData) => requestInfoData && _.keys(requestInfoData.content))),
258
- ]),
259
- );
260
-
261
- getContentKind = (contentTypes) => {
262
- if (
263
- _.some(contentTypes, (contentType) => _.startsWith(contentType, "application/json")) ||
264
- _.some(contentTypes, (contentType) => _.endsWith(contentType, "+json"))
265
- ) {
266
- return CONTENT_KIND.JSON;
267
- }
268
-
269
- if (contentTypes.includes("application/x-www-form-urlencoded")) {
270
- return CONTENT_KIND.URL_ENCODED;
271
- }
272
-
273
- if (contentTypes.includes("multipart/form-data")) {
274
- return CONTENT_KIND.FORM_DATA;
275
- }
276
-
277
- if (_.some(contentTypes, (contentType) => _.includes(contentType, "image/"))) {
278
- return CONTENT_KIND.IMAGE;
279
- }
280
-
281
- if (_.some(contentTypes, (contentType) => _.startsWith(contentType, "text/"))) {
282
- return CONTENT_KIND.TEXT;
283
- }
284
-
285
- return CONTENT_KIND.OTHER;
286
- };
287
-
288
- isSuccessStatus = (status) =>
289
- (this.config.defaultResponseAsSuccess && status === "default") ||
290
- (+status >= this.config.successResponseStatusRange[0] && +status <= this.config.successResponseStatusRange[1]) ||
291
- status === "2xx";
292
-
293
- getSchemaFromRequestType = (requestInfo) => {
294
- const content = _.get(requestInfo, "content");
295
-
296
- if (!content) return null;
297
-
298
- /* content: { "multipart/form-data": { schema: {...} }, "application/json": { schema: {...} } } */
299
-
300
- /* for example: dataType = "multipart/form-data" */
301
- for (const dataType in content) {
302
- if (content[dataType] && content[dataType].schema) {
303
- return {
304
- ...content[dataType].schema,
305
- dataType,
306
- };
307
- }
308
- }
309
-
310
- return null;
311
- };
312
-
313
- getTypeFromRequestInfo = async ({ requestInfo, parsedSchemas, operationId, defaultType, typeName }) => {
314
- // TODO: make more flexible pick schema without content type
315
- const schema = this.getSchemaFromRequestType(requestInfo);
316
- const refTypeInfo = this.schemaParser.schemaUtils.getSchemaRefType(requestInfo);
317
-
318
- if (schema) {
319
- const content = await this.schemaParser.getInlineParseContent(schema, typeName);
320
- const foundedSchemaByName = _.find(
321
- parsedSchemas,
322
- (parsedSchema) => this.typeName.format(parsedSchema.name) === content,
323
- );
324
- const foundSchemaByContent = _.find(parsedSchemas, (parsedSchema) => _.isEqual(parsedSchema.content, content));
325
-
326
- const foundSchema = foundedSchemaByName || foundSchemaByContent;
327
-
328
- return foundSchema ? this.typeName.format(foundSchema.name) : content;
329
- }
330
-
331
- if (refTypeInfo) {
332
- // const refTypeWithoutOpId = refType.replace(operationId, '');
333
- // const foundedSchemaByName = _.find(parsedSchemas, ({ name }) => name === refType || name === refTypeWithoutOpId)
334
-
335
- // TODO:HACK fix problem of swagger2opeanpi
336
- const typeNameWithoutOpId = _.replace(refTypeInfo.typeName, operationId, "");
337
- if (_.find(parsedSchemas, (schema) => schema.name === typeNameWithoutOpId)) {
338
- return this.typeName.format(typeNameWithoutOpId);
339
- }
340
-
341
- switch (refTypeInfo.componentName) {
342
- case "schemas":
343
- return this.typeName.format(refTypeInfo.typeName);
344
- case "responses":
345
- case "requestBodies":
346
- return await this.schemaParser.getInlineParseContent(
347
- this.getSchemaFromRequestType(refTypeInfo.rawTypeData),
348
- refTypeInfo.typeName || null,
349
- );
350
- default:
351
- return await this.schemaParser.getInlineParseContent(refTypeInfo.rawTypeData, refTypeInfo.typeName || null);
352
- }
353
- }
354
-
355
- return defaultType || this.config.Ts.Keyword.Any;
356
- };
357
-
358
- getRequestInfoTypes = async ({ requestInfos, parsedSchemas, operationId, defaultType }) => {
359
- const requestInfoTypes = [];
360
-
361
- const statuses = _.entries(requestInfos);
362
-
363
- for await (const [status, requestInfo] of statuses) {
364
- const contentTypes = this.getContentTypes([requestInfo]);
365
-
366
- const requestInfoType = {
367
- ...(requestInfo || {}),
368
- contentTypes: contentTypes,
369
- contentKind: this.getContentKind(contentTypes),
370
- type: this.schemaParser.schemaUtils.safeAddNullToType(
371
- requestInfo,
372
- await this.getTypeFromRequestInfo({
373
- requestInfo,
374
- parsedSchemas,
375
- operationId,
376
- defaultType,
377
- }),
378
- ),
379
- description: this.schemaParser.schemaFormatters.formatDescription(requestInfo.description || "", true),
380
- status: _.isNaN(+status) ? status : +status,
381
- isSuccess: this.isSuccessStatus(status),
382
- };
383
-
384
- requestInfoTypes.push(requestInfoType);
385
- }
386
-
387
- return requestInfoTypes;
388
- };
389
-
390
- getResponseBodyInfo = async (routeInfo, routeParams, parsedSchemas) => {
391
- const { produces, operationId, responses } = routeInfo;
392
-
393
- const contentTypes = this.getContentTypes(responses, [...(produces || []), routeInfo["x-accepts"]]);
394
-
395
- const responseInfos = await this.getRequestInfoTypes({
396
- requestInfos: responses,
397
- parsedSchemas,
398
- operationId,
399
- defaultType: this.config.defaultResponseType,
400
- });
401
-
402
- const successResponse = responseInfos.find((response) => response.isSuccess);
403
- const errorResponses = responseInfos.filter(
404
- (response) => !response.isSuccess && response.type !== this.config.Ts.Keyword.Any,
405
- );
406
-
407
- const handleResponseHeaders = async (src) => {
408
- if (!src) {
409
- return "headers: {},";
410
- }
411
- const headerTypes = Object.fromEntries(
412
- await Promise.all(
413
- Object.entries(src).map(async ([k, v]) => {
414
- return [k, await this.schemaUtils.getSchemaType(v)];
415
- }),
416
- ),
417
- );
418
- const r = `headers: { ${Object.entries(headerTypes)
419
- .map(([k, v]) => `"${k}": ${v}`)
420
- .join(",")} },`;
421
- return r;
422
- };
423
-
424
- return {
425
- contentTypes,
426
- responses: responseInfos,
427
- success: {
428
- schema: successResponse,
429
- type: (successResponse && successResponse.type) || this.config.Ts.Keyword.Any,
430
- },
431
- error: {
432
- schemas: errorResponses,
433
- type: this.config.Ts.UnionType(errorResponses.map((response) => response.type)) || this.config.Ts.Keyword.Any,
434
- },
435
- full: {
436
- types:
437
- this.config.Ts.UnionType(
438
- await Promise.all(
439
- responseInfos.map(
440
- async (response) => `{
441
- data: ${response.type}, status: ${response.status}, statusCode: ${response.status}, statusText: "${
442
- response.description
443
- }", ${await handleResponseHeaders(response.headers)} config: {} }`,
444
- ),
445
- ),
446
- ) || this.config.Ts.Keyword.Any,
447
- },
448
- };
449
- };
450
-
451
- convertRouteParamsIntoObject = (params) => {
452
- return _.reduce(
453
- params,
454
- (objectSchema, schemaPart) => {
455
- if (!schemaPart || !schemaPart.name) return objectSchema;
456
-
457
- return {
458
- ...objectSchema,
459
- properties: {
460
- ...objectSchema.properties,
461
- [schemaPart.name]: {
462
- ...schemaPart,
463
- ...(schemaPart.schema || {}),
464
- },
465
- },
466
- };
467
- },
468
- {
469
- properties: {},
470
- type: "object",
471
- },
472
- );
473
- };
474
-
475
- getRequestBodyInfo = async (routeInfo, routeParams, parsedSchemas, routeName) => {
476
- const { requestBody, consumes, requestBodyName, operationId } = routeInfo;
477
- let schema = null;
478
- let type = null;
479
-
480
- const contentTypes = this.getContentTypes([requestBody], [...(consumes || []), routeInfo["x-contentType"]]);
481
- let contentKind = this.getContentKind(contentTypes);
482
-
483
- let typeName = null;
484
-
485
- if (this.config.extractRequestBody) {
486
- typeName = this.schemaUtils.resolveTypeName(
487
- routeName.usage,
488
- this.config.extractingOptions.requestBodySuffix,
489
- this.config.extractingOptions.requestBodyNameResolver,
490
- );
491
- }
492
-
493
- if (routeParams.formData.length) {
494
- contentKind = CONTENT_KIND.FORM_DATA;
495
- schema = this.convertRouteParamsIntoObject(routeParams.formData);
496
- type = await this.schemaParser.getInlineParseContent(schema, typeName);
497
- } else if (contentKind === CONTENT_KIND.FORM_DATA) {
498
- schema = this.getSchemaFromRequestType(requestBody);
499
- type = await this.schemaParser.getInlineParseContent(schema, typeName);
500
- } else if (requestBody) {
501
- schema = this.getSchemaFromRequestType(requestBody);
502
- type = this.schemaParser.schemaUtils.safeAddNullToType(
503
- requestBody,
504
- await this.getTypeFromRequestInfo({
505
- requestInfo: requestBody,
506
- parsedSchemas,
507
- operationId,
508
- typeName,
509
- }),
510
- );
511
-
512
- // TODO: Refactor that.
513
- // It needed for cases when swagger schema is not declared request body type as form data
514
- // but request body data type contains form data types like File
515
- if (this.FORM_DATA_TYPES.some((dataType) => _.includes(type, `: ${dataType}`))) {
516
- contentKind = CONTENT_KIND.FORM_DATA;
517
- }
518
- }
519
-
520
- if (schema && !schema.$ref && this.config.extractRequestBody) {
521
- schema = this.schemaComponentMap.createComponent("schemas", typeName, { ...schema });
522
- type = await this.schemaParser.getInlineParseContent(schema);
523
- }
524
-
525
- return {
526
- paramName: requestBodyName || (requestBody && requestBody.name) || DEFAULT_BODY_ARG_NAME,
527
- contentTypes,
528
- contentKind,
529
- schema,
530
- type,
531
- required: requestBody && (typeof requestBody.required === "undefined" || !!requestBody.required),
532
- };
533
- };
534
-
535
- createRequestParamsSchema = ({
536
- queryParams,
537
- queryObjectSchema,
538
- pathArgsSchemas,
539
- extractRequestParams,
540
- routeName,
541
- }) => {
542
- if (!queryParams || !queryParams.length) return null;
543
-
544
- const pathParams = _.reduce(
545
- pathArgsSchemas,
546
- (acc, pathArgSchema) => {
547
- if (pathArgSchema.name) {
548
- acc[pathArgSchema.name] = {
549
- ...pathArgSchema,
550
- in: "path",
551
- };
552
- }
553
-
554
- return acc;
555
- },
556
- {},
557
- );
558
-
559
- const fixedQueryParams = _.reduce(
560
- _.get(queryObjectSchema, "properties", {}),
561
- (acc, property, name) => {
562
- if (name && _.isObject(property)) {
563
- acc[name] = {
564
- ...property,
565
- in: "query",
566
- };
567
- }
568
-
569
- return acc;
570
- },
571
- {},
572
- );
573
-
574
- const schema = {
575
- ...queryObjectSchema,
576
- properties: {
577
- ...fixedQueryParams,
578
- ...pathParams,
579
- },
580
- };
581
-
582
- const fixedSchema = this.config.hooks.onCreateRequestParams(schema);
583
-
584
- if (fixedSchema) return fixedSchema;
585
-
586
- if (extractRequestParams) {
587
- const typeName = this.schemaUtils.resolveTypeName(
588
- routeName.usage,
589
- this.config.extractingOptions.requestParamsSuffix,
590
- this.config.extractingOptions.requestParamsNameResolver,
591
- );
592
-
593
- return this.schemaComponentMap.createComponent("schemas", typeName, { ...schema });
594
- }
595
-
596
- return schema;
597
- };
598
-
599
- extractResponseBodyIfItNeeded = async (routeInfo, responseBodyInfo, routeName) => {
600
- if (responseBodyInfo.responses.length && responseBodyInfo.success && responseBodyInfo.success.schema) {
601
- const typeName = this.schemaUtils.resolveTypeName(
602
- routeName.usage,
603
- this.config.extractingOptions.responseBodySuffix,
604
- this.config.extractingOptions.responseBodyNameResolver,
605
- );
606
-
607
- const idx = responseBodyInfo.responses.indexOf(responseBodyInfo.success.schema);
608
-
609
- let successResponse = responseBodyInfo.success;
610
-
611
- if (successResponse.schema && !successResponse.schema.$ref) {
612
- const schema = this.getSchemaFromRequestType(successResponse.schema);
613
- successResponse.schema = this.schemaComponentMap.createComponent("schemas", typeName, { ...schema });
614
- successResponse.type = await this.schemaParser.getInlineParseContent(successResponse.schema);
615
-
616
- if (idx > -1) {
617
- _.assign(responseBodyInfo.responses[idx], {
618
- ...successResponse.schema,
619
- type: successResponse.type,
620
- });
621
- }
622
- }
623
- }
624
- };
625
-
626
- extractResponseErrorIfItNeeded = async (routeInfo, responseBodyInfo, routeName) => {
627
- if (responseBodyInfo.responses.length && responseBodyInfo.error.schemas && responseBodyInfo.error.schemas.length) {
628
- const typeName = this.schemaUtils.resolveTypeName(
629
- routeName.usage,
630
- this.config.extractingOptions.responseErrorSuffix,
631
- this.config.extractingOptions.responseErrorNameResolver,
632
- );
633
-
634
- const errorSchemas = responseBodyInfo.error.schemas.map(this.getSchemaFromRequestType).filter(Boolean);
635
-
636
- if (!errorSchemas.length) return;
637
-
638
- const schema = await this.schemaParser.parseSchema({
639
- oneOf: errorSchemas,
640
- title: errorSchemas
641
- .map((schema) => schema.title)
642
- .filter(Boolean)
643
- .join(" "),
644
- description: errorSchemas
645
- .map((schema) => schema.description)
646
- .filter(Boolean)
647
- .join("\n"),
648
- });
649
- const component = this.schemaComponentMap.createComponent("schemas", typeName, { ...schema });
650
- responseBodyInfo.error.schemas = [component];
651
- responseBodyInfo.error.type = this.typeName.format(component.typeName);
652
- }
653
- };
654
-
655
- getRouteName = async (rawRouteInfo) => {
656
- const { moduleName } = rawRouteInfo;
657
- const { routeNameDuplicatesMap, templatesToRender } = this.config;
658
- const routeNameTemplate = templatesToRender.routeName;
659
-
660
- const routeNameFromTemplate = await this.templates.renderTemplate(routeNameTemplate, {
661
- routeInfo: rawRouteInfo,
662
- });
663
-
664
- const routeName = this.config.hooks.onFormatRouteName(rawRouteInfo, routeNameFromTemplate) || routeNameFromTemplate;
665
-
666
- const duplicateIdentifier = `${moduleName}|${routeName}`;
667
-
668
- if (routeNameDuplicatesMap.has(duplicateIdentifier)) {
669
- routeNameDuplicatesMap.set(duplicateIdentifier, routeNameDuplicatesMap.get(duplicateIdentifier) + 1);
670
-
671
- this.logger.warn(
672
- `Module "${moduleName}" already has method "${routeName}()"`,
673
- `\nThis method has been renamed to "${
674
- routeName + routeNameDuplicatesMap.get(duplicateIdentifier)
675
- }()" to solve conflict names.`,
676
- );
677
- } else {
678
- routeNameDuplicatesMap.set(duplicateIdentifier, 1);
679
- }
680
-
681
- const duplicates = routeNameDuplicatesMap.get(duplicateIdentifier);
682
-
683
- const routeNameInfo = {
684
- usage: routeName + (duplicates > 1 ? duplicates : ""),
685
- original: routeName,
686
- duplicate: duplicates > 1,
687
- };
688
-
689
- return this.config.hooks.onCreateRouteName(routeNameInfo, rawRouteInfo) || routeNameInfo;
690
- };
691
-
692
- parseRouteInfo = async (rawRouteName, routeInfo, method, usageSchema, parsedSchemas) => {
693
- const { security: globalSecurity } = usageSchema;
694
- const { moduleNameIndex, moduleNameFirstTag, extractRequestParams } = this.config;
695
- const {
696
- operationId,
697
- requestBody,
698
- security,
699
- parameters,
700
- summary,
701
- description,
702
- tags,
703
- responses,
704
- requestBodyName,
705
- produces,
706
- consumes,
707
- ...otherInfo
708
- } = routeInfo;
709
- const {
710
- route,
711
- pathParams: pathParamsFromRouteName,
712
- queryParams: queryParamsFromRouteName,
713
- } = this.parseRouteName(rawRouteName);
714
-
715
- const routeId = generateId();
716
- const firstTag = tags && tags.length > 0 ? tags[0] : null;
717
- const moduleName =
718
- moduleNameFirstTag && firstTag
719
- ? _.camelCase(firstTag)
720
- : _.camelCase(_.compact(_.split(route, "/"))[moduleNameIndex]);
721
- let hasSecurity = !!(globalSecurity && globalSecurity.length);
722
- if (security) {
723
- hasSecurity = security.length > 0;
724
- }
725
-
726
- const routeParams = this.getRouteParams(routeInfo, pathParamsFromRouteName, queryParamsFromRouteName);
727
-
728
- const pathArgs = await Promise.all(
729
- routeParams.path.map(async (pathArgSchema) => ({
730
- name: pathArgSchema.name,
731
- optional: !pathArgSchema.required,
732
- type: await this.schemaParser.getInlineParseContent(pathArgSchema.schema),
733
- description: pathArgSchema.description,
734
- })),
735
- );
736
- const pathArgsNames = pathArgs.map((arg) => arg.name);
737
-
738
- const responseBodyInfo = await this.getResponseBodyInfo(routeInfo, routeParams, parsedSchemas);
739
-
740
- const rawRouteInfo = {
741
- ...otherInfo,
742
- pathArgs,
743
- operationId,
744
- method,
745
- route: rawRouteName,
746
- moduleName,
747
- responsesTypes: responseBodyInfo.responses,
748
- description,
749
- tags,
750
- summary,
751
- responses,
752
- produces,
753
- requestBody,
754
- consumes,
755
- };
756
-
757
- const queryObjectSchema = this.convertRouteParamsIntoObject(routeParams.query);
758
- const pathObjectSchema = this.convertRouteParamsIntoObject(routeParams.path);
759
- const headersObjectSchema = this.convertRouteParamsIntoObject(routeParams.header);
760
-
761
- const routeName = await this.getRouteName(rawRouteInfo);
762
-
763
- const requestBodyInfo = await this.getRequestBodyInfo(routeInfo, routeParams, parsedSchemas, routeName);
764
-
765
- const requestParamsSchema = this.createRequestParamsSchema({
766
- queryParams: routeParams.query,
767
- pathArgsSchemas: routeParams.path,
768
- queryObjectSchema,
769
- extractRequestParams,
770
- routeName,
771
- });
772
-
773
- if (this.config.extractResponseBody) {
774
- await this.extractResponseBodyIfItNeeded(routeInfo, responseBodyInfo, routeName);
775
- }
776
- if (this.config.extractResponseError) {
777
- await this.extractResponseErrorIfItNeeded(routeInfo, responseBodyInfo, routeName);
778
- }
779
-
780
- const queryType = routeParams.query.length
781
- ? await this.schemaParser.getInlineParseContent(queryObjectSchema)
782
- : null;
783
- const pathType = routeParams.path.length ? await this.schemaParser.getInlineParseContent(pathObjectSchema) : null;
784
- const headersType = routeParams.header.length
785
- ? await this.schemaParser.getInlineParseContent(headersObjectSchema)
786
- : null;
787
-
788
- const nameResolver = new SpecificArgNameResolver(this.logger, pathArgsNames);
789
-
790
- const specificArgs = {
791
- query: queryType
792
- ? {
793
- name: nameResolver.resolve(RESERVED_QUERY_ARG_NAMES),
794
- optional: (await this.schemaParser.parseSchema(queryObjectSchema)).allFieldsAreOptional,
795
- type: queryType,
796
- }
797
- : void 0,
798
- body: requestBodyInfo.type
799
- ? {
800
- name: nameResolver.resolve([requestBodyInfo.paramName, ...RESERVED_BODY_ARG_NAMES]),
801
- optional: !requestBodyInfo.required,
802
- type: requestBodyInfo.type,
803
- }
804
- : void 0,
805
- pathParams: pathType
806
- ? {
807
- name: nameResolver.resolve(RESERVED_PATH_ARG_NAMES),
808
- optional: (await this.schemaParser.parseSchema(pathObjectSchema)).allFieldsAreOptional,
809
- type: pathType,
810
- }
811
- : void 0,
812
- headers: headersType
813
- ? {
814
- name: nameResolver.resolve(RESERVED_HEADER_ARG_NAMES),
815
- optional: (await this.schemaParser.parseSchema(headersObjectSchema)).allFieldsAreOptional,
816
- type: headersType,
817
- }
818
- : void 0,
819
- };
820
-
821
- return {
822
- id: routeId,
823
- namespace: _.replace(moduleName, /^(\d)/, "v$1"),
824
- routeName,
825
- routeParams,
826
- requestBodyInfo,
827
- responseBodyInfo,
828
- specificArgs,
829
- queryObjectSchema,
830
- pathObjectSchema,
831
- headersObjectSchema,
832
- responseBodySchema: responseBodyInfo.success.schema,
833
- requestBodySchema: requestBodyInfo.schema,
834
- specificArgNameResolver: nameResolver,
835
- request: {
836
- contentTypes: requestBodyInfo.contentTypes,
837
- parameters: pathArgs,
838
- path: route,
839
- formData: requestBodyInfo.contentKind === CONTENT_KIND.FORM_DATA,
840
- isQueryBody: requestBodyInfo.contentKind === CONTENT_KIND.URL_ENCODED,
841
- security: hasSecurity,
842
- method: method,
843
- requestParams: requestParamsSchema,
844
-
845
- payload: specificArgs.body,
846
- query: specificArgs.query,
847
- pathParams: specificArgs.pathParams,
848
- headers: specificArgs.headers,
849
- },
850
- response: {
851
- contentTypes: responseBodyInfo.contentTypes,
852
- type: responseBodyInfo.success.type,
853
- errorType: responseBodyInfo.error.type,
854
- fullTypes: responseBodyInfo.full.types,
855
- },
856
- raw: rawRouteInfo,
857
- };
858
- };
859
-
860
- attachSchema = async ({ usageSchema, parsedSchemas }) => {
861
- this.config.routeNameDuplicatesMap.clear();
862
-
863
- const pathsEntries = _.entries(usageSchema.paths);
864
-
865
- for await (const [rawRouteName, routeInfoByMethodsMap] of pathsEntries) {
866
- const routeInfosMap = this.createRequestsMap(routeInfoByMethodsMap);
867
-
868
- for await (const [method, routeInfo] of _.entries(routeInfosMap)) {
869
- const parsedRouteInfo = await this.parseRouteInfo(rawRouteName, routeInfo, method, usageSchema, parsedSchemas);
870
- const processedRouteInfo = this.config.hooks.onCreateRoute(parsedRouteInfo);
871
- const route = processedRouteInfo || parsedRouteInfo;
872
-
873
- if (!this.hasSecurityRoutes && route.security) {
874
- this.hasSecurityRoutes = route.security;
875
- }
876
- if (!this.hasQueryRoutes && route.hasQuery) {
877
- this.hasQueryRoutes = route.hasQuery;
878
- }
879
- if (!this.hasFormDataRoutes && route.hasFormDataParams) {
880
- this.hasFormDataRoutes = route.hasFormDataParams;
881
- }
882
-
883
- this.routes.push(route);
884
- }
885
- }
886
- };
887
-
888
- getGroupedRoutes = () => {
889
- const groupedRoutes = this.routes.reduce(
890
- (modules, route) => {
891
- if (route.namespace) {
892
- if (!modules[route.namespace]) {
893
- modules[route.namespace] = [];
894
- }
895
-
896
- modules[route.namespace].push(route);
897
- } else {
898
- modules.$outOfModule.push(route);
899
- }
900
-
901
- return modules;
902
- },
903
- {
904
- $outOfModule: [],
905
- },
906
- );
907
-
908
- return _.reduce(
909
- groupedRoutes,
910
- (acc, routesGroup, moduleName) => {
911
- if (moduleName === "$outOfModule") {
912
- acc.outOfModule = routesGroup;
913
- } else {
914
- if (!acc.combined) acc.combined = [];
915
-
916
- acc.combined.push({
917
- moduleName,
918
- routes: _.map(routesGroup, (route) => {
919
- const { original: originalName, usage: usageName } = route.routeName;
920
-
921
- // TODO: https://github.com/acacode/swagger-typescript-api/issues/152
922
- // TODO: refactor
923
- if (
924
- routesGroup.length > 1 &&
925
- usageName !== originalName &&
926
- !_.some(routesGroup, ({ routeName, id }) => id !== route.id && originalName === routeName.original)
927
- ) {
928
- return {
929
- ...route,
930
- routeName: {
931
- ...route.routeName,
932
- usage: originalName,
933
- },
934
- };
935
- }
936
-
937
- return route;
938
- }),
939
- });
940
- }
941
- return acc;
942
- },
943
- {},
944
- );
945
- };
946
- }
947
-
948
- module.exports = {
949
- SchemaRoutes,
950
- };