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
@@ -0,0 +1,1225 @@
1
+ const _ = require('lodash');
2
+ const { generateId } = require('../util/id.js');
3
+ const {
4
+ SpecificArgNameResolver,
5
+ } = require('./util/specific-arg-name-resolver');
6
+ const {
7
+ DEFAULT_BODY_ARG_NAME,
8
+ RESERVED_BODY_ARG_NAMES,
9
+ RESERVED_HEADER_ARG_NAMES,
10
+ RESERVED_PATH_ARG_NAMES,
11
+ RESERVED_QUERY_ARG_NAMES,
12
+ } = require('../constants.js');
13
+ const { camelCase } = require('lodash');
14
+
15
+ const CONTENT_KIND = {
16
+ JSON: 'JSON',
17
+ URL_ENCODED: 'URL_ENCODED',
18
+ FORM_DATA: 'FORM_DATA',
19
+ IMAGE: 'IMAGE',
20
+ OTHER: 'OTHER',
21
+ TEXT: 'TEXT',
22
+ };
23
+
24
+ class SchemaRoutes {
25
+ /**
26
+ * @type {CodeGenConfig}
27
+ */
28
+ config;
29
+ /**
30
+ * @type {SchemaParserFabric}
31
+ */
32
+ schemaParserFabric;
33
+ /**
34
+ * @type {SchemaUtils}
35
+ */
36
+ schemaUtils;
37
+ /**
38
+ * @type {TypeNameFormatter}
39
+ */
40
+ typeNameFormatter;
41
+ /**
42
+ * @type {SchemaComponentsMap}
43
+ */
44
+ schemaComponentsMap;
45
+ /**
46
+ * @type {Logger}
47
+ */
48
+ logger;
49
+ /**
50
+ * @type {TemplatesWorker}
51
+ */
52
+ templatesWorker;
53
+
54
+ FORM_DATA_TYPES = [];
55
+
56
+ routes = [];
57
+ hasSecurityRoutes = false;
58
+ hasQueryRoutes = false;
59
+ hasFormDataRoutes = false;
60
+
61
+ constructor({
62
+ config,
63
+ schemaParserFabric,
64
+ schemaComponentsMap,
65
+ logger,
66
+ templatesWorker,
67
+ typeNameFormatter,
68
+ }) {
69
+ this.config = config;
70
+ this.schemaParserFabric = schemaParserFabric;
71
+ this.schemaUtils = this.schemaParserFabric.schemaUtils;
72
+ this.typeNameFormatter = typeNameFormatter;
73
+ this.schemaComponentsMap = schemaComponentsMap;
74
+ this.logger = logger;
75
+ this.templatesWorker = templatesWorker;
76
+
77
+ this.FORM_DATA_TYPES = _.uniq([
78
+ this.schemaUtils.getSchemaType({ type: 'string', format: 'file' }),
79
+ this.schemaUtils.getSchemaType({ type: 'string', format: 'binary' }),
80
+ ]);
81
+ }
82
+
83
+ createRequestsMap = (routeInfoByMethodsMap) => {
84
+ const parameters = _.get(routeInfoByMethodsMap, 'parameters');
85
+
86
+ return _.reduce(
87
+ routeInfoByMethodsMap,
88
+ (acc, requestInfo, method) => {
89
+ if (
90
+ _.startsWith(method, 'x-') ||
91
+ ['parameters', '$ref'].includes(method)
92
+ ) {
93
+ return acc;
94
+ }
95
+
96
+ acc[method] = {
97
+ ...requestInfo,
98
+ parameters: _.compact(_.concat(parameters, requestInfo.parameters)),
99
+ };
100
+
101
+ return acc;
102
+ },
103
+ {},
104
+ );
105
+ };
106
+
107
+ parseRouteName = (originalRouteName) => {
108
+ const routeName =
109
+ this.config.hooks.onPreBuildRoutePath(originalRouteName) ||
110
+ originalRouteName;
111
+
112
+ const pathParamMatches = (routeName || '').match(
113
+ /({(([A-z]){1}([a-zA-Z0-9]-?_?\.?)+)([0-9]+)?})|(:(([A-z]){1}([a-zA-Z0-9]-?_?\.?)+)([0-9]+)?:?)/g,
114
+ );
115
+
116
+ // used in case when path parameters is not declared in requestInfo.parameters ("in": "path")
117
+ const pathParams = _.reduce(
118
+ pathParamMatches,
119
+ (pathParams, match) => {
120
+ const paramName = _.replace(match, /\{|\}|:/g, '');
121
+
122
+ if (!paramName) return pathParams;
123
+
124
+ if (_.includes(paramName, '-')) {
125
+ this.logger.warn('wrong path param name', paramName);
126
+ }
127
+
128
+ pathParams.push({
129
+ $match: match,
130
+ name: _.camelCase(paramName),
131
+ required: true,
132
+ type: 'string',
133
+ description: '',
134
+ schema: {
135
+ type: 'string',
136
+ },
137
+ in: 'path',
138
+ });
139
+
140
+ return pathParams;
141
+ },
142
+ [],
143
+ );
144
+
145
+ let fixedRoute = _.reduce(
146
+ pathParams,
147
+ (fixedRoute, pathParam, i, arr) => {
148
+ const insertion =
149
+ this.config.hooks.onInsertPathParam(
150
+ pathParam.name,
151
+ i,
152
+ arr,
153
+ fixedRoute,
154
+ ) || pathParam.name;
155
+ return _.replace(fixedRoute, pathParam.$match, `\${${insertion}}`);
156
+ },
157
+ routeName || '',
158
+ );
159
+
160
+ const queryParamMatches = fixedRoute.match(/(\{\?.*\})/g);
161
+ const queryParams = [];
162
+
163
+ if (queryParamMatches && queryParamMatches.length) {
164
+ queryParamMatches.forEach((match) => {
165
+ fixedRoute = fixedRoute.replace(match, '');
166
+ });
167
+
168
+ _.uniq(
169
+ queryParamMatches
170
+ .join(',')
171
+ .replace(/(\{\?)|(\})|\s/g, '')
172
+ .split(','),
173
+ ).forEach((paramName) => {
174
+ if (_.includes(paramName, '-')) {
175
+ this.logger.warn('wrong query param name', paramName);
176
+ }
177
+
178
+ queryParams.push({
179
+ $match: paramName,
180
+ name: _.camelCase(paramName),
181
+ required: true,
182
+ type: 'string',
183
+ description: '',
184
+ schema: {
185
+ type: 'string',
186
+ },
187
+ in: 'query',
188
+ });
189
+ });
190
+ }
191
+
192
+ const result = {
193
+ originalRoute: originalRouteName || '',
194
+ route: fixedRoute,
195
+ pathParams,
196
+ queryParams,
197
+ };
198
+
199
+ return this.config.hooks.onBuildRoutePath(result) || result;
200
+ };
201
+
202
+ getRouteParams = (
203
+ routeInfo,
204
+ pathParamsFromRouteName,
205
+ queryParamsFromRouteName,
206
+ ) => {
207
+ const { parameters } = routeInfo;
208
+
209
+ const routeParams = {
210
+ path: [],
211
+ header: [],
212
+ body: [],
213
+ query: [],
214
+ formData: [],
215
+ cookie: [],
216
+ };
217
+
218
+ _.each(parameters, (parameter) => {
219
+ const refTypeInfo =
220
+ this.schemaParserFabric.schemaUtils.getSchemaRefType(parameter);
221
+ let routeParam = null;
222
+
223
+ if (
224
+ refTypeInfo &&
225
+ refTypeInfo.rawTypeData.in &&
226
+ refTypeInfo.rawTypeData
227
+ ) {
228
+ if (!routeParams[refTypeInfo.rawTypeData.in]) {
229
+ routeParams[refTypeInfo.rawTypeData.in] = [];
230
+ }
231
+
232
+ routeParam = {
233
+ ...refTypeInfo.rawTypeData,
234
+ ...(refTypeInfo.rawTypeData.schema || {}),
235
+ };
236
+ } else {
237
+ if (!parameter.in) return;
238
+
239
+ if (!routeParams[parameter.in]) {
240
+ routeParams[parameter.in] = [];
241
+ }
242
+
243
+ routeParam = {
244
+ ...parameter,
245
+ ...(parameter.schema || {}),
246
+ };
247
+ }
248
+
249
+ if (routeParam.in === 'path') {
250
+ if (!routeParam.name) return;
251
+
252
+ routeParam.name = _.camelCase(routeParam.name);
253
+ }
254
+
255
+ if (routeParam) {
256
+ routeParams[routeParam.in].push(routeParam);
257
+ }
258
+ });
259
+
260
+ // used in case when path parameters is not declared in requestInfo.parameters ("in": "path")
261
+ _.each(pathParamsFromRouteName, (pathParam) => {
262
+ const alreadyExist = _.some(
263
+ routeParams.path,
264
+ (parameter) => parameter.name === pathParam.name,
265
+ );
266
+
267
+ if (!alreadyExist) {
268
+ routeParams.path.push(pathParam);
269
+ }
270
+ });
271
+ // used in case when path parameters is not declared in requestInfo.parameters ("in": "path")
272
+ _.each(queryParamsFromRouteName, (queryParam) => {
273
+ const alreadyExist = _.some(
274
+ routeParams.query,
275
+ (parameter) => parameter.name === queryParam.name,
276
+ );
277
+
278
+ if (!alreadyExist) {
279
+ routeParams.query.push(queryParam);
280
+ }
281
+ });
282
+
283
+ return routeParams;
284
+ };
285
+
286
+ getContentTypes = (requestInfo, extraContentTypes) =>
287
+ _.uniq(
288
+ _.compact([
289
+ ...(extraContentTypes || []),
290
+ ..._.flatten(
291
+ _.map(
292
+ requestInfo,
293
+ (requestInfoData) =>
294
+ requestInfoData && _.keys(requestInfoData.content),
295
+ ),
296
+ ),
297
+ ]),
298
+ );
299
+
300
+ getContentKind = (contentTypes) => {
301
+ if (
302
+ _.some(contentTypes, (contentType) =>
303
+ _.startsWith(contentType, 'application/json'),
304
+ ) ||
305
+ _.some(contentTypes, (contentType) => _.endsWith(contentType, '+json'))
306
+ ) {
307
+ return CONTENT_KIND.JSON;
308
+ }
309
+
310
+ if (contentTypes.includes('application/x-www-form-urlencoded')) {
311
+ return CONTENT_KIND.URL_ENCODED;
312
+ }
313
+
314
+ if (contentTypes.includes('multipart/form-data')) {
315
+ return CONTENT_KIND.FORM_DATA;
316
+ }
317
+
318
+ if (
319
+ _.some(contentTypes, (contentType) => _.includes(contentType, 'image/'))
320
+ ) {
321
+ return CONTENT_KIND.IMAGE;
322
+ }
323
+
324
+ if (
325
+ _.some(contentTypes, (contentType) => _.startsWith(contentType, 'text/'))
326
+ ) {
327
+ return CONTENT_KIND.TEXT;
328
+ }
329
+
330
+ return CONTENT_KIND.OTHER;
331
+ };
332
+
333
+ isSuccessStatus = (status) =>
334
+ (this.config.defaultResponseAsSuccess && status === 'default') ||
335
+ (+status >= this.config.successResponseStatusRange[0] &&
336
+ +status <= this.config.successResponseStatusRange[1]) ||
337
+ status === '2xx';
338
+
339
+ getSchemaFromRequestType = (requestInfo) => {
340
+ const content = _.get(requestInfo, 'content');
341
+
342
+ if (!content) return null;
343
+
344
+ /* content: { "multipart/form-data": { schema: {...} }, "application/json": { schema: {...} } } */
345
+
346
+ /* for example: dataType = "multipart/form-data" */
347
+ for (const dataType in content) {
348
+ if (content[dataType] && content[dataType].schema) {
349
+ return {
350
+ ...content[dataType].schema,
351
+ dataType,
352
+ };
353
+ }
354
+ }
355
+
356
+ return null;
357
+ };
358
+
359
+ getTypeFromRequestInfo = ({
360
+ requestInfo,
361
+ parsedSchemas,
362
+ operationId,
363
+ defaultType,
364
+ typeName,
365
+ }) => {
366
+ // TODO: make more flexible pick schema without content type
367
+ const schema = this.getSchemaFromRequestType(requestInfo);
368
+ const refTypeInfo =
369
+ this.schemaParserFabric.schemaUtils.getSchemaRefType(requestInfo);
370
+
371
+ if (schema) {
372
+ const content = this.schemaParserFabric.getInlineParseContent(
373
+ schema,
374
+ typeName,
375
+ [operationId],
376
+ );
377
+ const foundedSchemaByName = _.find(
378
+ parsedSchemas,
379
+ (parsedSchema) =>
380
+ this.typeNameFormatter.format(parsedSchema.name) === content,
381
+ );
382
+ const foundSchemaByContent = _.find(parsedSchemas, (parsedSchema) =>
383
+ _.isEqual(parsedSchema.content, content),
384
+ );
385
+
386
+ const foundSchema = foundedSchemaByName || foundSchemaByContent;
387
+
388
+ return foundSchema
389
+ ? this.typeNameFormatter.format(foundSchema.name)
390
+ : content;
391
+ }
392
+
393
+ if (refTypeInfo) {
394
+ // const refTypeWithoutOpId = refType.replace(operationId, '');
395
+ // const foundedSchemaByName = _.find(parsedSchemas, ({ name }) => name === refType || name === refTypeWithoutOpId)
396
+
397
+ // TODO:HACK fix problem of swagger2opeanpi
398
+ const typeNameWithoutOpId = _.replace(
399
+ refTypeInfo.typeName,
400
+ operationId,
401
+ '',
402
+ );
403
+ if (
404
+ _.find(parsedSchemas, (schema) => schema.name === typeNameWithoutOpId)
405
+ ) {
406
+ return this.typeNameFormatter.format(typeNameWithoutOpId);
407
+ }
408
+
409
+ switch (refTypeInfo.componentName) {
410
+ case 'schemas':
411
+ return this.typeNameFormatter.format(refTypeInfo.typeName);
412
+ case 'responses':
413
+ case 'requestBodies':
414
+ return this.schemaParserFabric.getInlineParseContent(
415
+ this.getSchemaFromRequestType(refTypeInfo.rawTypeData),
416
+ refTypeInfo.typeName || null,
417
+ [operationId],
418
+ );
419
+ default:
420
+ return this.schemaParserFabric.getInlineParseContent(
421
+ refTypeInfo.rawTypeData,
422
+ refTypeInfo.typeName || null,
423
+ [operationId],
424
+ );
425
+ }
426
+ }
427
+
428
+ return defaultType || this.config.Ts.Keyword.Any;
429
+ };
430
+
431
+ getRequestInfoTypes = ({
432
+ requestInfos,
433
+ parsedSchemas,
434
+ operationId,
435
+ defaultType,
436
+ }) =>
437
+ _.reduce(
438
+ requestInfos,
439
+ (acc, requestInfo, status) => {
440
+ const contentTypes = this.getContentTypes([requestInfo]);
441
+
442
+ return [
443
+ ...acc,
444
+ {
445
+ ...(requestInfo || {}),
446
+ contentTypes: contentTypes,
447
+ contentKind: this.getContentKind(contentTypes),
448
+ type: this.schemaParserFabric.schemaUtils.safeAddNullToType(
449
+ requestInfo,
450
+ this.getTypeFromRequestInfo({
451
+ requestInfo,
452
+ parsedSchemas,
453
+ operationId,
454
+ defaultType,
455
+ }),
456
+ ),
457
+ description:
458
+ this.schemaParserFabric.schemaFormatters.formatDescription(
459
+ requestInfo.description || '',
460
+ true,
461
+ ),
462
+ status: _.isNaN(+status) ? status : +status,
463
+ isSuccess: this.isSuccessStatus(status),
464
+ },
465
+ ];
466
+ },
467
+ [],
468
+ );
469
+
470
+ getResponseBodyInfo = (routeInfo, parsedSchemas) => {
471
+ const { produces, operationId, responses } = routeInfo;
472
+
473
+ const contentTypes = this.getContentTypes(responses, [
474
+ ...(produces || []),
475
+ routeInfo['x-accepts'],
476
+ ]);
477
+
478
+ const responseInfos = this.getRequestInfoTypes({
479
+ requestInfos: responses,
480
+ parsedSchemas,
481
+ operationId,
482
+ defaultType: this.config.defaultResponseType,
483
+ });
484
+
485
+ const successResponse = responseInfos.find(
486
+ (response) => response.isSuccess,
487
+ );
488
+ const errorResponses = responseInfos.filter(
489
+ (response) =>
490
+ !response.isSuccess && response.type !== this.config.Ts.Keyword.Any,
491
+ );
492
+
493
+ const handleResponseHeaders = (src) => {
494
+ if (!src) {
495
+ return 'headers: {},';
496
+ }
497
+ const headerTypes = Object.fromEntries(
498
+ Object.entries(src).map(([k, v]) => {
499
+ return [k, this.schemaUtils.getSchemaType(v)];
500
+ }),
501
+ );
502
+ const r = `headers: { ${Object.entries(headerTypes)
503
+ .map(([k, v]) => `"${k}": ${v}`)
504
+ .join(',')} },`;
505
+ return r;
506
+ };
507
+
508
+ return {
509
+ contentTypes,
510
+ responses: responseInfos,
511
+ success: {
512
+ schema: successResponse,
513
+ type:
514
+ (successResponse && successResponse.type) ||
515
+ this.config.Ts.Keyword.Any,
516
+ },
517
+ error: {
518
+ schemas: errorResponses,
519
+ type:
520
+ this.config.Ts.UnionType(
521
+ errorResponses.map((response) => response.type),
522
+ ) || this.config.Ts.Keyword.Any,
523
+ },
524
+ full: {
525
+ types:
526
+ this.config.Ts.UnionType(
527
+ responseInfos.map(
528
+ (response) => `{
529
+ data: ${response.type}, status: ${response.status}, statusCode: ${
530
+ response.status
531
+ }, statusText: "${response.description}", ${handleResponseHeaders(
532
+ response.headers,
533
+ )} config: {} }`,
534
+ ),
535
+ ) || this.config.Ts.Keyword.Any,
536
+ },
537
+ };
538
+ };
539
+
540
+ convertRouteParamsIntoObject = (params) => {
541
+ return _.reduce(
542
+ params,
543
+ (objectSchema, schemaPart) => {
544
+ if (!schemaPart || !schemaPart.name) return objectSchema;
545
+
546
+ let usageName = `${schemaPart.name}`;
547
+
548
+ if (usageName.includes('.')) {
549
+ usageName = camelCase(usageName);
550
+ }
551
+
552
+ return {
553
+ ...objectSchema,
554
+ properties: {
555
+ ...objectSchema.properties,
556
+ [usageName]: {
557
+ ...schemaPart,
558
+ ...(schemaPart.schema || {}),
559
+ $origName: schemaPart.name,
560
+ name: usageName,
561
+ },
562
+ },
563
+ };
564
+ },
565
+ {
566
+ properties: {},
567
+ type: 'object',
568
+ },
569
+ );
570
+ };
571
+
572
+ getRequestBodyInfo = (routeInfo, routeParams, parsedSchemas, routeName) => {
573
+ const { requestBody, consumes, requestBodyName, operationId } = routeInfo;
574
+ let schema = null;
575
+ let content = null;
576
+
577
+ const contentTypes = this.getContentTypes(
578
+ [requestBody],
579
+ [...(consumes || []), routeInfo['x-contentType']],
580
+ );
581
+ let contentKind = this.getContentKind(contentTypes);
582
+
583
+ let typeName = null;
584
+
585
+ if (this.config.extractRequestBody) {
586
+ typeName = this.schemaUtils.resolveTypeName(routeName.usage, {
587
+ suffixes: this.config.extractingOptions.requestBodySuffix,
588
+ resolver: this.config.extractingOptions.requestBodyNameResolver,
589
+ });
590
+ }
591
+
592
+ if (routeParams.formData.length) {
593
+ contentKind = CONTENT_KIND.FORM_DATA;
594
+ schema = this.convertRouteParamsIntoObject(routeParams.formData);
595
+ content = this.schemaParserFabric.getInlineParseContent(
596
+ schema,
597
+ typeName,
598
+ [operationId],
599
+ );
600
+ } else if (contentKind === CONTENT_KIND.FORM_DATA) {
601
+ schema = this.getSchemaFromRequestType(requestBody);
602
+ content = this.schemaParserFabric.getInlineParseContent(
603
+ schema,
604
+ typeName,
605
+ [operationId],
606
+ );
607
+ } else if (requestBody) {
608
+ schema = this.getSchemaFromRequestType(requestBody);
609
+ content = this.schemaParserFabric.schemaUtils.safeAddNullToType(
610
+ requestBody,
611
+ this.getTypeFromRequestInfo({
612
+ requestInfo: requestBody,
613
+ parsedSchemas,
614
+ operationId,
615
+ typeName,
616
+ }),
617
+ );
618
+
619
+ // TODO: Refactor that.
620
+ // It needed for cases when swagger schema is not declared request body type as form data
621
+ // but request body data type contains form data types like File
622
+ if (
623
+ this.FORM_DATA_TYPES.some((dataType) =>
624
+ _.includes(content, `: ${dataType}`),
625
+ )
626
+ ) {
627
+ contentKind = CONTENT_KIND.FORM_DATA;
628
+ }
629
+ }
630
+
631
+ if (schema && !schema.$ref && this.config.extractRequestBody) {
632
+ schema = this.schemaParserFabric.createParsedComponent({
633
+ schema,
634
+ typeName,
635
+ schemaPath: [operationId],
636
+ });
637
+ content = this.schemaParserFabric.getInlineParseContent({
638
+ $ref: schema.$ref,
639
+ });
640
+ }
641
+
642
+ return {
643
+ paramName:
644
+ requestBodyName ||
645
+ (requestBody && requestBody.name) ||
646
+ DEFAULT_BODY_ARG_NAME,
647
+ contentTypes,
648
+ contentKind,
649
+ schema,
650
+ type: content,
651
+ required:
652
+ requestBody &&
653
+ (typeof requestBody.required === 'undefined' || !!requestBody.required),
654
+ };
655
+ };
656
+
657
+ createRequestParamsSchema = ({
658
+ queryParams,
659
+ queryObjectSchema,
660
+ pathArgsSchemas,
661
+ extractRequestParams,
662
+ routeName,
663
+ }) => {
664
+ if (!queryParams || !queryParams.length) return null;
665
+
666
+ const pathParams = _.reduce(
667
+ pathArgsSchemas,
668
+ (acc, pathArgSchema) => {
669
+ if (pathArgSchema.name) {
670
+ acc[pathArgSchema.name] = {
671
+ ...pathArgSchema,
672
+ in: 'path',
673
+ };
674
+ }
675
+
676
+ return acc;
677
+ },
678
+ {},
679
+ );
680
+
681
+ const fixedQueryParams = _.reduce(
682
+ _.get(queryObjectSchema, 'properties', {}),
683
+ (acc, property, name) => {
684
+ if (name && _.isObject(property)) {
685
+ acc[name] = {
686
+ ...property,
687
+ in: 'query',
688
+ };
689
+ }
690
+
691
+ return acc;
692
+ },
693
+ {},
694
+ );
695
+
696
+ const schema = {
697
+ ...queryObjectSchema,
698
+ properties: {
699
+ ...fixedQueryParams,
700
+ ...pathParams,
701
+ },
702
+ };
703
+
704
+ const fixedSchema = this.config.hooks.onCreateRequestParams(schema);
705
+
706
+ if (fixedSchema) return fixedSchema;
707
+
708
+ if (extractRequestParams) {
709
+ const generatedTypeName = this.schemaUtils.resolveTypeName(
710
+ routeName.usage,
711
+ {
712
+ suffixes: this.config.extractingOptions.requestParamsSuffix,
713
+ resolver: this.config.extractingOptions.requestParamsNameResolver,
714
+ },
715
+ );
716
+
717
+ return this.schemaParserFabric.createParsedComponent({
718
+ typeName: generatedTypeName,
719
+ schema: schema,
720
+ });
721
+ }
722
+
723
+ return schema;
724
+ };
725
+
726
+ extractResponseBodyIfItNeeded = (routeInfo, responseBodyInfo, routeName) => {
727
+ if (
728
+ responseBodyInfo.responses.length &&
729
+ responseBodyInfo.success &&
730
+ responseBodyInfo.success.schema
731
+ ) {
732
+ const typeName = this.schemaUtils.resolveTypeName(routeName.usage, {
733
+ suffixes: this.config.extractingOptions.responseBodySuffix,
734
+ resolver: this.config.extractingOptions.responseBodyNameResolver,
735
+ });
736
+
737
+ const idx = responseBodyInfo.responses.indexOf(
738
+ responseBodyInfo.success.schema,
739
+ );
740
+
741
+ let successResponse = responseBodyInfo.success;
742
+
743
+ if (successResponse.schema && !successResponse.schema.$ref) {
744
+ const schema = this.getSchemaFromRequestType(successResponse.schema);
745
+ successResponse.schema = this.schemaParserFabric.createParsedComponent({
746
+ schema,
747
+ typeName,
748
+ schemaPath: [routeInfo.operationId],
749
+ });
750
+ successResponse.type = this.schemaParserFabric.getInlineParseContent({
751
+ $ref: successResponse.schema.$ref,
752
+ });
753
+
754
+ if (idx > -1) {
755
+ _.assign(responseBodyInfo.responses[idx], {
756
+ ...successResponse.schema,
757
+ type: successResponse.type,
758
+ });
759
+ }
760
+ }
761
+ }
762
+ };
763
+
764
+ extractResponseErrorIfItNeeded = (routeInfo, responseBodyInfo, routeName) => {
765
+ if (
766
+ responseBodyInfo.responses.length &&
767
+ responseBodyInfo.error.schemas &&
768
+ responseBodyInfo.error.schemas.length
769
+ ) {
770
+ const typeName = this.schemaUtils.resolveTypeName(routeName.usage, {
771
+ suffixes: this.config.extractingOptions.responseErrorSuffix,
772
+ resolver: this.config.extractingOptions.responseErrorNameResolver,
773
+ });
774
+
775
+ const errorSchemas = responseBodyInfo.error.schemas
776
+ .map(this.getSchemaFromRequestType)
777
+ .filter(Boolean);
778
+
779
+ if (!errorSchemas.length) return;
780
+
781
+ const schema = this.schemaParserFabric.parseSchema(
782
+ {
783
+ oneOf: errorSchemas,
784
+ title: errorSchemas
785
+ .map((schema) => schema.title)
786
+ .filter(Boolean)
787
+ .join(' '),
788
+ description: errorSchemas
789
+ .map((schema) => schema.description)
790
+ .filter(Boolean)
791
+ .join('\n'),
792
+ },
793
+ null,
794
+ [routeInfo.operationId],
795
+ );
796
+ const component = this.schemaComponentsMap.createComponent(
797
+ this.schemaComponentsMap.createRef(['components', 'schemas', typeName]),
798
+ { ...schema },
799
+ );
800
+ responseBodyInfo.error.schemas = [component];
801
+ responseBodyInfo.error.type = this.typeNameFormatter.format(
802
+ component.typeName,
803
+ );
804
+ }
805
+ };
806
+
807
+ getRouteName = (rawRouteInfo) => {
808
+ const { moduleName } = rawRouteInfo;
809
+ const { routeNameDuplicatesMap, templatesToRender } = this.config;
810
+ const routeNameTemplate = templatesToRender.routeName;
811
+
812
+ const routeNameFromTemplate = this.templatesWorker.renderTemplate(
813
+ routeNameTemplate,
814
+ {
815
+ routeInfo: rawRouteInfo,
816
+ },
817
+ );
818
+
819
+ const routeName =
820
+ this.config.hooks.onFormatRouteName(
821
+ rawRouteInfo,
822
+ routeNameFromTemplate,
823
+ ) || routeNameFromTemplate;
824
+
825
+ const duplicateIdentifier = `${moduleName}|${routeName}`;
826
+
827
+ if (routeNameDuplicatesMap.has(duplicateIdentifier)) {
828
+ routeNameDuplicatesMap.set(
829
+ duplicateIdentifier,
830
+ routeNameDuplicatesMap.get(duplicateIdentifier) + 1,
831
+ );
832
+
833
+ this.logger.warn(
834
+ `Module "${moduleName}" already has method "${routeName}()"`,
835
+ `\nThis method has been renamed to "${
836
+ routeName + routeNameDuplicatesMap.get(duplicateIdentifier)
837
+ }()" to solve conflict names.`,
838
+ );
839
+ } else {
840
+ routeNameDuplicatesMap.set(duplicateIdentifier, 1);
841
+ }
842
+
843
+ const duplicates = routeNameDuplicatesMap.get(duplicateIdentifier);
844
+
845
+ const routeNameInfo = {
846
+ usage: routeName + (duplicates > 1 ? duplicates : ''),
847
+ original: routeName,
848
+ duplicate: duplicates > 1,
849
+ };
850
+
851
+ return (
852
+ this.config.hooks.onCreateRouteName(routeNameInfo, rawRouteInfo) ||
853
+ routeNameInfo
854
+ );
855
+ };
856
+
857
+ parseRouteInfo = (
858
+ rawRouteName,
859
+ routeInfo,
860
+ method,
861
+ usageSchema,
862
+ parsedSchemas,
863
+ ) => {
864
+ const { security: globalSecurity } = usageSchema;
865
+ const { moduleNameIndex, moduleNameFirstTag, extractRequestParams } =
866
+ this.config;
867
+ const {
868
+ operationId,
869
+ requestBody,
870
+ security,
871
+ // eslint-disable-next-line no-unused-vars
872
+ parameters,
873
+ summary,
874
+ description,
875
+ tags,
876
+ responses,
877
+ // eslint-disable-next-line no-unused-vars
878
+ requestBodyName,
879
+ produces,
880
+ consumes,
881
+ ...otherInfo
882
+ } = routeInfo;
883
+ const {
884
+ route,
885
+ pathParams: pathParamsFromRouteName,
886
+ queryParams: queryParamsFromRouteName,
887
+ } = this.parseRouteName(rawRouteName);
888
+
889
+ const routeId = generateId();
890
+ const firstTag = tags && tags.length > 0 ? tags[0] : null;
891
+ const moduleName =
892
+ moduleNameFirstTag && firstTag
893
+ ? _.camelCase(firstTag)
894
+ : _.camelCase(_.compact(_.split(route, '/'))[moduleNameIndex]);
895
+ let hasSecurity = !!(globalSecurity && globalSecurity.length);
896
+ if (security) {
897
+ hasSecurity = security.length > 0;
898
+ }
899
+
900
+ const routeParams = this.getRouteParams(
901
+ routeInfo,
902
+ pathParamsFromRouteName,
903
+ queryParamsFromRouteName,
904
+ );
905
+
906
+ const pathArgs = routeParams.path.map((pathArgSchema) => ({
907
+ name: pathArgSchema.name,
908
+ optional: !pathArgSchema.required,
909
+ // mark it as any for now, because "getInlineParseContent" breaks type names of extracted enums
910
+ type: this.config.Ts.Keyword.Any,
911
+ description: pathArgSchema.description,
912
+ }));
913
+ const pathArgsNames = pathArgs.map((arg) => arg.name);
914
+
915
+ const responseBodyInfo = this.getResponseBodyInfo(routeInfo, parsedSchemas);
916
+
917
+ const rawRouteInfo = {
918
+ ...otherInfo,
919
+ pathArgs,
920
+ operationId,
921
+ method,
922
+ route: rawRouteName,
923
+ moduleName,
924
+ responsesTypes: responseBodyInfo.responses,
925
+ description,
926
+ tags,
927
+ summary,
928
+ responses,
929
+ produces,
930
+ requestBody,
931
+ consumes,
932
+ };
933
+
934
+ const queryObjectSchema = this.convertRouteParamsIntoObject(
935
+ routeParams.query,
936
+ );
937
+ const pathObjectSchema = this.convertRouteParamsIntoObject(
938
+ routeParams.path,
939
+ );
940
+ const headersObjectSchema = this.convertRouteParamsIntoObject(
941
+ routeParams.header,
942
+ );
943
+
944
+ const routeName = this.getRouteName(rawRouteInfo);
945
+
946
+ const requestBodyInfo = this.getRequestBodyInfo(
947
+ routeInfo,
948
+ routeParams,
949
+ parsedSchemas,
950
+ routeName,
951
+ );
952
+
953
+ const requestParamsSchema = this.createRequestParamsSchema({
954
+ queryParams: routeParams.query,
955
+ pathArgsSchemas: routeParams.path,
956
+ queryObjectSchema,
957
+ extractRequestParams,
958
+ routeName,
959
+ });
960
+
961
+ if (this.config.extractResponseBody) {
962
+ this.extractResponseBodyIfItNeeded(
963
+ routeInfo,
964
+ responseBodyInfo,
965
+ routeName,
966
+ );
967
+ }
968
+ if (this.config.extractResponseError) {
969
+ this.extractResponseErrorIfItNeeded(
970
+ routeInfo,
971
+ responseBodyInfo,
972
+ routeName,
973
+ );
974
+ }
975
+
976
+ const typeName = this.schemaUtils.resolveTypeName(routeName.usage, {
977
+ suffixes: this.config.extractingOptions.requestParamsSuffix,
978
+ resolver: this.config.extractingOptions.requestParamsNameResolver,
979
+ shouldReserve: false,
980
+ });
981
+
982
+ const queryType = routeParams.query.length
983
+ ? this.schemaParserFabric.getInlineParseContent(queryObjectSchema, null, [
984
+ typeName,
985
+ ])
986
+ : null;
987
+ const pathType = routeParams.path.length
988
+ ? this.schemaParserFabric.getInlineParseContent(pathObjectSchema, null, [
989
+ typeName,
990
+ ])
991
+ : null;
992
+ const headersType = routeParams.header.length
993
+ ? this.schemaParserFabric.getInlineParseContent(
994
+ headersObjectSchema,
995
+ null,
996
+ [typeName],
997
+ )
998
+ : null;
999
+
1000
+ const nameResolver = new SpecificArgNameResolver(
1001
+ this.config,
1002
+ this.logger,
1003
+ pathArgsNames,
1004
+ );
1005
+
1006
+ const specificArgs = {
1007
+ query: queryType
1008
+ ? {
1009
+ name: nameResolver.resolve(RESERVED_QUERY_ARG_NAMES),
1010
+ optional: this.schemaParserFabric.parseSchema(
1011
+ queryObjectSchema,
1012
+ null,
1013
+ [routeName.usage],
1014
+ ).allFieldsAreOptional,
1015
+ type: queryType,
1016
+ }
1017
+ : void 0,
1018
+ body: requestBodyInfo.type
1019
+ ? {
1020
+ name: nameResolver.resolve([
1021
+ requestBodyInfo.paramName,
1022
+ ...RESERVED_BODY_ARG_NAMES,
1023
+ ]),
1024
+ optional: !requestBodyInfo.required,
1025
+ type: requestBodyInfo.type,
1026
+ }
1027
+ : void 0,
1028
+ pathParams: pathType
1029
+ ? {
1030
+ name: nameResolver.resolve(RESERVED_PATH_ARG_NAMES),
1031
+ optional: this.schemaParserFabric.parseSchema(
1032
+ pathObjectSchema,
1033
+ null,
1034
+ [routeName.usage],
1035
+ ).allFieldsAreOptional,
1036
+ type: pathType,
1037
+ }
1038
+ : void 0,
1039
+ headers: headersType
1040
+ ? {
1041
+ name: nameResolver.resolve(RESERVED_HEADER_ARG_NAMES),
1042
+ optional: this.schemaParserFabric.parseSchema(
1043
+ headersObjectSchema,
1044
+ null,
1045
+ [routeName.usage],
1046
+ ).allFieldsAreOptional,
1047
+ type: headersType,
1048
+ }
1049
+ : void 0,
1050
+ };
1051
+
1052
+ pathArgs.forEach((pathArg, i) => {
1053
+ pathArg.type = this.schemaParserFabric.getInlineParseContent(
1054
+ routeParams.path[i].schema,
1055
+ null,
1056
+ [typeName],
1057
+ );
1058
+ });
1059
+
1060
+ return {
1061
+ id: routeId,
1062
+ namespace: _.replace(moduleName, /^(\d)/, 'v$1'),
1063
+ routeName,
1064
+ routeParams,
1065
+ requestBodyInfo,
1066
+ responseBodyInfo,
1067
+ specificArgs,
1068
+ queryObjectSchema,
1069
+ pathObjectSchema,
1070
+ headersObjectSchema,
1071
+ responseBodySchema: responseBodyInfo.success.schema,
1072
+ requestBodySchema: requestBodyInfo.schema,
1073
+ specificArgNameResolver: nameResolver,
1074
+ request: {
1075
+ contentTypes: requestBodyInfo.contentTypes,
1076
+ parameters: pathArgs,
1077
+ path: route,
1078
+ formData: requestBodyInfo.contentKind === CONTENT_KIND.FORM_DATA,
1079
+ isQueryBody: requestBodyInfo.contentKind === CONTENT_KIND.URL_ENCODED,
1080
+ security: hasSecurity,
1081
+ method: method,
1082
+ requestParams: requestParamsSchema,
1083
+
1084
+ payload: specificArgs.body,
1085
+ query: specificArgs.query,
1086
+ pathParams: specificArgs.pathParams,
1087
+ headers: specificArgs.headers,
1088
+ },
1089
+ response: {
1090
+ contentTypes: responseBodyInfo.contentTypes,
1091
+ type: responseBodyInfo.success.type,
1092
+ errorType: responseBodyInfo.error.type,
1093
+ fullTypes: responseBodyInfo.full.types,
1094
+ },
1095
+ raw: rawRouteInfo,
1096
+ };
1097
+ };
1098
+
1099
+ attachSchema = ({ usageSchema, parsedSchemas }) => {
1100
+ this.config.routeNameDuplicatesMap.clear();
1101
+
1102
+ const pathsEntries = _.entries(usageSchema.paths);
1103
+
1104
+ _.forEach(pathsEntries, ([rawRouteName, routeInfoByMethodsMap]) => {
1105
+ const routeInfosMap = this.createRequestsMap(routeInfoByMethodsMap);
1106
+
1107
+ _.forEach(routeInfosMap, (routeInfo, method) => {
1108
+ const parsedRouteInfo = this.parseRouteInfo(
1109
+ rawRouteName,
1110
+ routeInfo,
1111
+ method,
1112
+ usageSchema,
1113
+ parsedSchemas,
1114
+ );
1115
+ const processedRouteInfo =
1116
+ this.config.hooks.onCreateRoute(parsedRouteInfo);
1117
+ if (processedRouteInfo !== false) {
1118
+ const route = processedRouteInfo || parsedRouteInfo;
1119
+
1120
+ if (!this.hasSecurityRoutes && route.security) {
1121
+ this.hasSecurityRoutes = route.security;
1122
+ }
1123
+ if (!this.hasQueryRoutes && route.hasQuery) {
1124
+ this.hasQueryRoutes = route.hasQuery;
1125
+ }
1126
+ if (!this.hasFormDataRoutes && route.hasFormDataParams) {
1127
+ this.hasFormDataRoutes = route.hasFormDataParams;
1128
+ }
1129
+
1130
+ this.routes.push(route);
1131
+ }
1132
+ });
1133
+ });
1134
+ };
1135
+
1136
+ getGroupedRoutes = () => {
1137
+ const groupedRoutes = this.routes.reduce(
1138
+ (modules, route) => {
1139
+ if (route.namespace) {
1140
+ if (!modules[route.namespace]) {
1141
+ modules[route.namespace] = [];
1142
+ }
1143
+
1144
+ modules[route.namespace].push(route);
1145
+ } else {
1146
+ modules.$outOfModule.push(route);
1147
+ }
1148
+
1149
+ return modules;
1150
+ },
1151
+ {
1152
+ $outOfModule: [],
1153
+ },
1154
+ );
1155
+
1156
+ const routeGroups = _.reduce(
1157
+ groupedRoutes,
1158
+ (acc, routesGroup, moduleName) => {
1159
+ if (moduleName === '$outOfModule') {
1160
+ acc.outOfModule = routesGroup;
1161
+ } else {
1162
+ if (!acc.combined) acc.combined = [];
1163
+
1164
+ acc.combined.push({
1165
+ moduleName,
1166
+ routes: _.map(routesGroup, (route) => {
1167
+ const { original: originalName, usage: usageName } =
1168
+ route.routeName;
1169
+
1170
+ // TODO: https://github.com/acacode/swagger-typescript-api/issues/152
1171
+ // TODO: refactor
1172
+ if (
1173
+ routesGroup.length > 1 &&
1174
+ usageName !== originalName &&
1175
+ !_.some(
1176
+ routesGroup,
1177
+ ({ routeName, id }) =>
1178
+ id !== route.id && originalName === routeName.original,
1179
+ )
1180
+ ) {
1181
+ return {
1182
+ ...route,
1183
+ routeName: {
1184
+ ...route.routeName,
1185
+ usage: originalName,
1186
+ },
1187
+ };
1188
+ }
1189
+
1190
+ return route;
1191
+ }),
1192
+ });
1193
+ }
1194
+ return acc;
1195
+ },
1196
+ {},
1197
+ );
1198
+
1199
+ if (this.config.sortRoutes) {
1200
+ if (routeGroups.outOfModule) {
1201
+ routeGroups.outOfModule = this.sortRoutes(routeGroups.outOfModule);
1202
+ }
1203
+ if (routeGroups.combined) {
1204
+ routeGroups.combined = this.sortRoutes(routeGroups.combined);
1205
+ }
1206
+ }
1207
+
1208
+ return routeGroups;
1209
+ };
1210
+
1211
+ sortRoutes = (routeInfo) => {
1212
+ if (routeInfo) {
1213
+ routeInfo.forEach((routeInfo) => {
1214
+ routeInfo.routes.sort((routeA, routeB) =>
1215
+ routeA.routeName.usage.localeCompare(routeB.routeName.usage),
1216
+ );
1217
+ });
1218
+ }
1219
+ return routeInfo;
1220
+ };
1221
+ }
1222
+
1223
+ module.exports = {
1224
+ SchemaRoutes,
1225
+ };