swagger-typescript-api 10.0.2 → 11.0.0--alpha

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