@tsoa-next/cli 7.1.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 (122) hide show
  1. package/README.MD +3 -0
  2. package/dist/cli.d.ts +44 -0
  3. package/dist/cli.js +356 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.js +25 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/metadataGeneration/controllerGenerator.d.ts +30 -0
  9. package/dist/metadataGeneration/controllerGenerator.js +229 -0
  10. package/dist/metadataGeneration/controllerGenerator.js.map +1 -0
  11. package/dist/metadataGeneration/exceptions.d.ts +13 -0
  12. package/dist/metadataGeneration/exceptions.js +53 -0
  13. package/dist/metadataGeneration/exceptions.js.map +1 -0
  14. package/dist/metadataGeneration/extension.d.ts +5 -0
  15. package/dist/metadataGeneration/extension.js +85 -0
  16. package/dist/metadataGeneration/extension.js.map +1 -0
  17. package/dist/metadataGeneration/initializer-value.d.ts +6 -0
  18. package/dist/metadataGeneration/initializer-value.js +154 -0
  19. package/dist/metadataGeneration/initializer-value.js.map +1 -0
  20. package/dist/metadataGeneration/metadataGenerator.d.ts +29 -0
  21. package/dist/metadataGeneration/metadataGenerator.js +220 -0
  22. package/dist/metadataGeneration/metadataGenerator.js.map +1 -0
  23. package/dist/metadataGeneration/methodGenerator.d.ts +45 -0
  24. package/dist/metadataGeneration/methodGenerator.js +367 -0
  25. package/dist/metadataGeneration/methodGenerator.js.map +1 -0
  26. package/dist/metadataGeneration/parameterGenerator.d.ts +33 -0
  27. package/dist/metadataGeneration/parameterGenerator.js +552 -0
  28. package/dist/metadataGeneration/parameterGenerator.js.map +1 -0
  29. package/dist/metadataGeneration/transformer/dateTransformer.d.ts +6 -0
  30. package/dist/metadataGeneration/transformer/dateTransformer.js +28 -0
  31. package/dist/metadataGeneration/transformer/dateTransformer.js.map +1 -0
  32. package/dist/metadataGeneration/transformer/enumTransformer.d.ts +12 -0
  33. package/dist/metadataGeneration/transformer/enumTransformer.js +75 -0
  34. package/dist/metadataGeneration/transformer/enumTransformer.js.map +1 -0
  35. package/dist/metadataGeneration/transformer/primitiveTransformer.d.ts +11 -0
  36. package/dist/metadataGeneration/transformer/primitiveTransformer.js +70 -0
  37. package/dist/metadataGeneration/transformer/primitiveTransformer.js.map +1 -0
  38. package/dist/metadataGeneration/transformer/propertyTransformer.d.ts +12 -0
  39. package/dist/metadataGeneration/transformer/propertyTransformer.js +101 -0
  40. package/dist/metadataGeneration/transformer/propertyTransformer.js.map +1 -0
  41. package/dist/metadataGeneration/transformer/referenceTransformer.d.ts +10 -0
  42. package/dist/metadataGeneration/transformer/referenceTransformer.js +81 -0
  43. package/dist/metadataGeneration/transformer/referenceTransformer.js.map +1 -0
  44. package/dist/metadataGeneration/transformer/transformer.d.ts +9 -0
  45. package/dist/metadataGeneration/transformer/transformer.js +39 -0
  46. package/dist/metadataGeneration/transformer/transformer.js.map +1 -0
  47. package/dist/metadataGeneration/typeResolver.d.ts +52 -0
  48. package/dist/metadataGeneration/typeResolver.js +1202 -0
  49. package/dist/metadataGeneration/typeResolver.js.map +1 -0
  50. package/dist/module/generate-routes.d.ts +9 -0
  51. package/dist/module/generate-routes.js +90 -0
  52. package/dist/module/generate-routes.js.map +1 -0
  53. package/dist/module/generate-spec.d.ts +9 -0
  54. package/dist/module/generate-spec.js +79 -0
  55. package/dist/module/generate-spec.js.map +1 -0
  56. package/dist/routeGeneration/defaultRouteGenerator.d.ts +12 -0
  57. package/dist/routeGeneration/defaultRouteGenerator.js +119 -0
  58. package/dist/routeGeneration/defaultRouteGenerator.js.map +1 -0
  59. package/dist/routeGeneration/routeGenerator.d.ts +56 -0
  60. package/dist/routeGeneration/routeGenerator.js +257 -0
  61. package/dist/routeGeneration/routeGenerator.js.map +1 -0
  62. package/dist/routeGeneration/templates/express.hbs +221 -0
  63. package/dist/routeGeneration/templates/hapi.hbs +267 -0
  64. package/dist/routeGeneration/templates/koa.hbs +218 -0
  65. package/dist/swagger/specGenerator.d.ts +33 -0
  66. package/dist/swagger/specGenerator.js +253 -0
  67. package/dist/swagger/specGenerator.js.map +1 -0
  68. package/dist/swagger/specGenerator2.d.ts +27 -0
  69. package/dist/swagger/specGenerator2.js +476 -0
  70. package/dist/swagger/specGenerator2.js.map +1 -0
  71. package/dist/swagger/specGenerator3.d.ts +158 -0
  72. package/dist/swagger/specGenerator3.js +646 -0
  73. package/dist/swagger/specGenerator3.js.map +1 -0
  74. package/dist/swagger/specGenerator31.d.ts +24 -0
  75. package/dist/swagger/specGenerator31.js +75 -0
  76. package/dist/swagger/specGenerator31.js.map +1 -0
  77. package/dist/utils/decoratorUtils.d.ts +9 -0
  78. package/dist/utils/decoratorUtils.js +118 -0
  79. package/dist/utils/decoratorUtils.js.map +1 -0
  80. package/dist/utils/flowUtils.d.ts +1 -0
  81. package/dist/utils/flowUtils.js +8 -0
  82. package/dist/utils/flowUtils.js.map +1 -0
  83. package/dist/utils/fs.d.ts +5 -0
  84. package/dist/utils/fs.js +55 -0
  85. package/dist/utils/fs.js.map +1 -0
  86. package/dist/utils/genericTypeGuards.d.ts +1 -0
  87. package/dist/utils/genericTypeGuards.js +8 -0
  88. package/dist/utils/genericTypeGuards.js.map +1 -0
  89. package/dist/utils/headerTypeHelpers.d.ts +5 -0
  90. package/dist/utils/headerTypeHelpers.js +27 -0
  91. package/dist/utils/headerTypeHelpers.js.map +1 -0
  92. package/dist/utils/importClassesFromDirectories.d.ts +4 -0
  93. package/dist/utils/importClassesFromDirectories.js +20 -0
  94. package/dist/utils/importClassesFromDirectories.js.map +1 -0
  95. package/dist/utils/internalTypeGuards.d.ts +5 -0
  96. package/dist/utils/internalTypeGuards.js +66 -0
  97. package/dist/utils/internalTypeGuards.js.map +1 -0
  98. package/dist/utils/isVoidType.d.ts +2 -0
  99. package/dist/utils/isVoidType.js +16 -0
  100. package/dist/utils/isVoidType.js.map +1 -0
  101. package/dist/utils/jsDocUtils.d.ts +8 -0
  102. package/dist/utils/jsDocUtils.js +122 -0
  103. package/dist/utils/jsDocUtils.js.map +1 -0
  104. package/dist/utils/jsonUtils.d.ts +1 -0
  105. package/dist/utils/jsonUtils.js +12 -0
  106. package/dist/utils/jsonUtils.js.map +1 -0
  107. package/dist/utils/pathUtils.d.ts +9 -0
  108. package/dist/utils/pathUtils.js +37 -0
  109. package/dist/utils/pathUtils.js.map +1 -0
  110. package/dist/utils/specMerge.d.ts +2 -0
  111. package/dist/utils/specMerge.js +36 -0
  112. package/dist/utils/specMerge.js.map +1 -0
  113. package/dist/utils/swaggerUtils.d.ts +3 -0
  114. package/dist/utils/swaggerUtils.js +22 -0
  115. package/dist/utils/swaggerUtils.js.map +1 -0
  116. package/dist/utils/unspecifiedObject.d.ts +3 -0
  117. package/dist/utils/unspecifiedObject.js +3 -0
  118. package/dist/utils/unspecifiedObject.js.map +1 -0
  119. package/dist/utils/validatorUtils.d.ts +5 -0
  120. package/dist/utils/validatorUtils.js +241 -0
  121. package/dist/utils/validatorUtils.js.map +1 -0
  122. package/package.json +69 -0
@@ -0,0 +1,646 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpecGenerator3 = void 0;
4
+ const runtime_1 = require("@tsoa-next/runtime");
5
+ const ts_deepmerge_1 = require("ts-deepmerge");
6
+ const isVoidType_1 = require("../utils/isVoidType");
7
+ const specMerge_1 = require("../utils/specMerge");
8
+ const validatorUtils_1 = require("../utils/validatorUtils");
9
+ const pathUtils_1 = require("./../utils/pathUtils");
10
+ const swaggerUtils_1 = require("./../utils/swaggerUtils");
11
+ const specGenerator_1 = require("./specGenerator");
12
+ /**
13
+ * TODO:
14
+ * Handle formData parameters
15
+ * Handle requestBodies of type other than json
16
+ * Handle requestBodies as reusable objects
17
+ * Handle headers, examples, responses, etc.
18
+ * Cleaner interface between SpecGenerator2 and SpecGenerator3
19
+ * Also accept OpenAPI 3.0.0 metadata, like components/securitySchemes instead of securityDefinitions
20
+ */
21
+ class SpecGenerator3 extends specGenerator_1.SpecGenerator {
22
+ metadata;
23
+ config;
24
+ constructor(metadata, config) {
25
+ super(metadata, config);
26
+ this.metadata = metadata;
27
+ this.config = config;
28
+ }
29
+ GetSpec() {
30
+ let spec = {
31
+ openapi: '3.0.0',
32
+ components: this.buildComponents(),
33
+ info: this.buildInfo(),
34
+ paths: this.buildPaths(),
35
+ servers: this.buildServers(),
36
+ tags: this.config.tags,
37
+ };
38
+ if (this.config.spec) {
39
+ this.config.specMerging = this.config.specMerging || 'immediate';
40
+ const mergeFuncs = {
41
+ immediate: Object.assign,
42
+ recursive: specMerge_1.recursiveMerge,
43
+ deepmerge: (spec, merge) => (0, ts_deepmerge_1.merge)(spec, merge),
44
+ };
45
+ spec = mergeFuncs[this.config.specMerging](spec, this.config.spec);
46
+ }
47
+ return spec;
48
+ }
49
+ buildInfo() {
50
+ const info = {
51
+ title: this.config.name || '',
52
+ };
53
+ if (this.config.version) {
54
+ info.version = this.config.version;
55
+ }
56
+ if (this.config.description) {
57
+ info.description = this.config.description;
58
+ }
59
+ if (this.config.termsOfService) {
60
+ info.termsOfService = this.config.termsOfService;
61
+ }
62
+ if (this.config.license) {
63
+ info.license = { name: this.config.license };
64
+ }
65
+ if (this.config.contact) {
66
+ info.contact = this.config.contact;
67
+ }
68
+ return info;
69
+ }
70
+ buildComponents() {
71
+ const components = {
72
+ examples: {},
73
+ headers: {},
74
+ parameters: {},
75
+ requestBodies: {},
76
+ responses: {},
77
+ schemas: this.buildSchema(),
78
+ securitySchemes: {},
79
+ };
80
+ if (this.config.securityDefinitions) {
81
+ components.securitySchemes = this.translateSecurityDefinitions(this.config.securityDefinitions);
82
+ }
83
+ return components;
84
+ }
85
+ translateSecurityDefinitions(definitions) {
86
+ const defs = {};
87
+ Object.keys(definitions).forEach(key => {
88
+ if (definitions[key].type === 'basic') {
89
+ defs[key] = {
90
+ scheme: 'basic',
91
+ type: 'http',
92
+ };
93
+ }
94
+ else if (definitions[key].type === 'oauth2') {
95
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
96
+ const definition = definitions[key];
97
+ const oauth = (defs[key] || {
98
+ type: 'oauth2',
99
+ description: definitions[key].description,
100
+ flows: (this.hasOAuthFlows(definition) && definition.flows) || {},
101
+ });
102
+ if (this.hasOAuthFlow(definition) && definition.flow === 'password') {
103
+ oauth.flows.password = { tokenUrl: definition.tokenUrl, scopes: definition.scopes || {} };
104
+ }
105
+ else if (this.hasOAuthFlow(definition) && definition.flow === 'accessCode') {
106
+ oauth.flows.authorizationCode = { tokenUrl: definition.tokenUrl, authorizationUrl: definition.authorizationUrl, scopes: definition.scopes || {} };
107
+ }
108
+ else if (this.hasOAuthFlow(definition) && definition.flow === 'application') {
109
+ oauth.flows.clientCredentials = { tokenUrl: definition.tokenUrl, scopes: definition.scopes || {} };
110
+ }
111
+ else if (this.hasOAuthFlow(definition) && definition.flow === 'implicit') {
112
+ oauth.flows.implicit = { authorizationUrl: definition.authorizationUrl, scopes: definition.scopes || {} };
113
+ }
114
+ defs[key] = oauth;
115
+ }
116
+ else {
117
+ defs[key] = definitions[key];
118
+ }
119
+ });
120
+ return defs;
121
+ }
122
+ hasOAuthFlow(definition) {
123
+ return !!definition.flow;
124
+ }
125
+ hasOAuthFlows(definition) {
126
+ return !!definition.flows;
127
+ }
128
+ buildServers() {
129
+ const prefix = this.config.disableBasePathPrefixSlash ? undefined : '/';
130
+ const basePath = (0, pathUtils_1.normalisePath)(this.config.basePath, prefix, undefined, false);
131
+ const scheme = this.config.schemes ? this.config.schemes[0] : 'https';
132
+ const hosts = this.config.servers ? this.config.servers : this.config.host ? [this.config.host] : undefined;
133
+ const convertHost = (host) => ({ url: `${scheme}://${host}${basePath}` });
134
+ return (hosts?.map(convertHost) || [{ url: basePath }]);
135
+ }
136
+ buildSchema() {
137
+ const schema = {};
138
+ Object.keys(this.metadata.referenceTypeMap).map(typeName => {
139
+ const referenceType = this.metadata.referenceTypeMap[typeName];
140
+ if (referenceType.dataType === 'refObject') {
141
+ const required = referenceType.properties.filter(p => this.isRequiredWithoutDefault(p) && !this.hasUndefined(p)).map(p => p.name);
142
+ schema[referenceType.refName] = {
143
+ description: referenceType.description,
144
+ properties: this.buildProperties(referenceType.properties),
145
+ required: required && required.length > 0 ? Array.from(new Set(required)) : undefined,
146
+ type: 'object',
147
+ };
148
+ if (referenceType.additionalProperties) {
149
+ schema[referenceType.refName].additionalProperties = this.buildAdditionalProperties(referenceType.additionalProperties);
150
+ }
151
+ else {
152
+ // Since additionalProperties was not explicitly set in the TypeScript interface for this model
153
+ // ...we need to make a decision
154
+ schema[referenceType.refName].additionalProperties = this.determineImplicitAdditionalPropertiesValue();
155
+ }
156
+ if (referenceType.example) {
157
+ schema[referenceType.refName].example = referenceType.example;
158
+ }
159
+ if (referenceType.title) {
160
+ schema[referenceType.refName].title = referenceType.title;
161
+ }
162
+ }
163
+ else if (referenceType.dataType === 'refEnum') {
164
+ const enumTypes = this.determineTypesUsedInEnum(referenceType.enums);
165
+ if (enumTypes.size === 1) {
166
+ schema[referenceType.refName] = {
167
+ description: referenceType.description,
168
+ enum: referenceType.enums,
169
+ type: enumTypes.has('string') ? 'string' : 'number',
170
+ };
171
+ if (this.config.xEnumVarnames && referenceType.enumVarnames !== undefined && referenceType.enums.length === referenceType.enumVarnames.length) {
172
+ schema[referenceType.refName]['x-enum-varnames'] = referenceType.enumVarnames;
173
+ }
174
+ }
175
+ else {
176
+ schema[referenceType.refName] = {
177
+ description: referenceType.description,
178
+ anyOf: [
179
+ {
180
+ type: 'number',
181
+ enum: referenceType.enums.filter(e => typeof e === 'number'),
182
+ },
183
+ {
184
+ type: 'string',
185
+ enum: referenceType.enums.filter(e => typeof e === 'string'),
186
+ },
187
+ ],
188
+ };
189
+ }
190
+ if (referenceType.example) {
191
+ schema[referenceType.refName].example = referenceType.example;
192
+ }
193
+ if (referenceType.title) {
194
+ schema[referenceType.refName].title = referenceType.title;
195
+ }
196
+ }
197
+ else if (referenceType.dataType === 'refAlias') {
198
+ const swaggerType = this.getSwaggerType(referenceType.type);
199
+ const format = referenceType.format;
200
+ const validators = Object.keys(referenceType.validators)
201
+ .filter(validatorUtils_1.shouldIncludeValidatorInSchema)
202
+ .reduce((acc, key) => {
203
+ return {
204
+ ...acc,
205
+ [key]: referenceType.validators[key].value,
206
+ };
207
+ }, {});
208
+ schema[referenceType.refName] = {
209
+ ...swaggerType,
210
+ default: referenceType.default || swaggerType.default,
211
+ example: referenceType.example,
212
+ format: format || swaggerType.format,
213
+ description: referenceType.description,
214
+ ...(referenceType.title && { title: referenceType.title }),
215
+ ...validators,
216
+ };
217
+ }
218
+ else {
219
+ (0, runtime_1.assertNever)(referenceType);
220
+ }
221
+ if (referenceType.deprecated) {
222
+ schema[referenceType.refName].deprecated = true;
223
+ }
224
+ });
225
+ return schema;
226
+ }
227
+ buildPaths() {
228
+ const paths = {};
229
+ this.metadata.controllers.forEach(controller => {
230
+ const normalisedControllerPath = (0, pathUtils_1.normalisePath)(controller.path, '/');
231
+ // construct documentation using all methods except @Hidden
232
+ controller.methods
233
+ .filter(method => !method.isHidden)
234
+ .forEach(method => {
235
+ const normalisedMethodPath = (0, pathUtils_1.normalisePath)(method.path, '/');
236
+ let path = (0, pathUtils_1.normalisePath)(`${normalisedControllerPath}${normalisedMethodPath}`, '/', '', false);
237
+ path = (0, pathUtils_1.convertColonPathParams)(path);
238
+ paths[path] = paths[path] || {};
239
+ this.buildMethod(controller.name, method, paths[path], controller.produces);
240
+ });
241
+ });
242
+ return paths;
243
+ }
244
+ buildMethod(controllerName, method, pathObject, defaultProduces) {
245
+ const pathMethod = (pathObject[method.method] = this.buildOperation(controllerName, method, defaultProduces));
246
+ pathMethod.description = method.description;
247
+ pathMethod.summary = method.summary;
248
+ pathMethod.tags = method.tags;
249
+ // Use operationId tag otherwise fallback to generated. Warning: This doesn't check uniqueness.
250
+ pathMethod.operationId = method.operationId || pathMethod.operationId;
251
+ if (method.deprecated) {
252
+ pathMethod.deprecated = method.deprecated;
253
+ }
254
+ if (method.security) {
255
+ pathMethod.security = method.security;
256
+ }
257
+ const bodyParams = method.parameters.filter(p => p.in === 'body');
258
+ const bodyPropParams = method.parameters.filter(p => p.in === 'body-prop');
259
+ const formParams = method.parameters.filter(p => p.in === 'formData');
260
+ const queriesParams = method.parameters.filter(p => p.in === 'queries');
261
+ pathMethod.parameters = method.parameters
262
+ .filter(p => {
263
+ return ['body', 'formData', 'request', 'body-prop', 'res', 'queries', 'request-prop'].indexOf(p.in) === -1;
264
+ })
265
+ .map(p => this.buildParameter(p));
266
+ if (queriesParams.length > 1) {
267
+ throw new Error('Only one queries parameter allowed per controller method.');
268
+ }
269
+ if (queriesParams.length === 1) {
270
+ pathMethod.parameters.push(...this.buildQueriesParameter(queriesParams[0]));
271
+ }
272
+ if (bodyParams.length > 1) {
273
+ throw new Error('Only one body parameter allowed per controller method.');
274
+ }
275
+ if (bodyParams.length > 0 && formParams.length > 0) {
276
+ throw new Error('Either body parameter or form parameters allowed per controller method - not both.');
277
+ }
278
+ if (bodyPropParams.length > 0) {
279
+ if (!bodyParams.length) {
280
+ bodyParams.push({
281
+ in: 'body',
282
+ name: 'body',
283
+ parameterName: 'body',
284
+ required: true,
285
+ type: {
286
+ dataType: 'nestedObjectLiteral',
287
+ properties: [],
288
+ },
289
+ validators: {},
290
+ deprecated: false,
291
+ });
292
+ }
293
+ const type = bodyParams[0].type;
294
+ bodyPropParams.forEach((bodyParam) => {
295
+ type.properties.push(bodyParam);
296
+ });
297
+ }
298
+ if (bodyParams.length > 0) {
299
+ pathMethod.requestBody = this.buildRequestBody(controllerName, method, bodyParams[0]);
300
+ }
301
+ else if (formParams.length > 0) {
302
+ pathMethod.requestBody = this.buildRequestBodyWithFormData(controllerName, method, formParams);
303
+ }
304
+ method.extensions.forEach(ext => (pathMethod[ext.key] = ext.value));
305
+ }
306
+ buildOperation(controllerName, method, defaultProduces) {
307
+ const swaggerResponses = {};
308
+ method.responses.forEach((res) => {
309
+ swaggerResponses[res.name] = {
310
+ description: res.description,
311
+ };
312
+ if (res.schema && !(0, isVoidType_1.isVoidType)(res.schema)) {
313
+ swaggerResponses[res.name].content = {};
314
+ const produces = res.produces || defaultProduces || [swaggerUtils_1.DEFAULT_RESPONSE_MEDIA_TYPE];
315
+ for (const p of produces) {
316
+ const { content } = swaggerResponses[res.name];
317
+ swaggerResponses[res.name].content = {
318
+ ...content,
319
+ [p]: {
320
+ schema: this.getSwaggerType(res.schema, this.config.useTitleTagsForInlineObjects ? this.getOperationId(controllerName, method) + 'Response' : undefined),
321
+ },
322
+ };
323
+ }
324
+ if (res.examples) {
325
+ let exampleCounter = 1;
326
+ const examples = res.examples.reduce((acc, ex, currentIndex) => {
327
+ const exampleLabel = res.exampleLabels?.[currentIndex];
328
+ return { ...acc, [exampleLabel === undefined ? `Example ${exampleCounter++}` : exampleLabel]: { value: ex } };
329
+ }, {});
330
+ for (const p of produces) {
331
+ /* eslint-disable @typescript-eslint/dot-notation */
332
+ ;
333
+ (swaggerResponses[res.name].content || {})[p]['examples'] = examples;
334
+ }
335
+ }
336
+ }
337
+ if (res.headers) {
338
+ const headers = {};
339
+ if (res.headers.dataType === 'refObject') {
340
+ headers[res.headers.refName] = {
341
+ schema: this.getSwaggerTypeForReferenceType(res.headers),
342
+ description: res.headers.description,
343
+ };
344
+ }
345
+ else if (res.headers.dataType === 'nestedObjectLiteral') {
346
+ res.headers.properties.forEach((each) => {
347
+ headers[each.name] = {
348
+ schema: this.getSwaggerType(each.type),
349
+ description: each.description,
350
+ required: this.isRequiredWithoutDefault(each),
351
+ };
352
+ });
353
+ }
354
+ else {
355
+ (0, runtime_1.assertNever)(res.headers);
356
+ }
357
+ swaggerResponses[res.name].headers = headers;
358
+ }
359
+ });
360
+ const operation = {
361
+ operationId: this.getOperationId(controllerName, method),
362
+ responses: swaggerResponses,
363
+ };
364
+ return operation;
365
+ }
366
+ buildRequestBodyWithFormData(controllerName, method, parameters) {
367
+ const required = [];
368
+ const properties = {};
369
+ for (const parameter of parameters) {
370
+ const mediaType = this.buildMediaType(controllerName, method, parameter);
371
+ properties[parameter.name] = mediaType.schema;
372
+ if (this.isRequiredWithoutDefault(parameter)) {
373
+ required.push(parameter.name);
374
+ }
375
+ if (parameter.deprecated) {
376
+ properties[parameter.name].deprecated = parameter.deprecated;
377
+ }
378
+ }
379
+ const requestBody = {
380
+ required: required.length > 0,
381
+ content: {
382
+ 'multipart/form-data': {
383
+ schema: {
384
+ type: 'object',
385
+ properties,
386
+ // An empty list required: [] is not valid.
387
+ // If all properties are optional, do not specify the required keyword.
388
+ ...(required && required.length && { required }),
389
+ },
390
+ },
391
+ },
392
+ };
393
+ return requestBody;
394
+ }
395
+ buildRequestBody(controllerName, method, parameter) {
396
+ const mediaType = this.buildMediaType(controllerName, method, parameter);
397
+ const consumes = method.consumes || swaggerUtils_1.DEFAULT_REQUEST_MEDIA_TYPE;
398
+ const requestBody = {
399
+ description: parameter.description,
400
+ required: this.isRequiredWithoutDefault(parameter),
401
+ content: {
402
+ [consumes]: mediaType,
403
+ },
404
+ };
405
+ return requestBody;
406
+ }
407
+ buildMediaType(controllerName, method, parameter) {
408
+ const validators = Object.keys(parameter.validators)
409
+ .filter(validatorUtils_1.shouldIncludeValidatorInSchema)
410
+ .reduce((acc, key) => {
411
+ return {
412
+ ...acc,
413
+ [key]: parameter.validators[key].value,
414
+ };
415
+ }, {});
416
+ const mediaType = {
417
+ schema: {
418
+ ...this.getSwaggerType(parameter.type, this.config.useTitleTagsForInlineObjects ? this.getOperationId(controllerName, method) + 'RequestBody' : undefined),
419
+ ...validators,
420
+ ...(parameter.description && { description: parameter.description }),
421
+ },
422
+ };
423
+ const parameterExamples = parameter.example;
424
+ const parameterExampleLabels = parameter.exampleLabels;
425
+ if (parameterExamples === undefined) {
426
+ mediaType.example = parameterExamples;
427
+ }
428
+ else if (parameterExamples.length === 1) {
429
+ mediaType.example = parameterExamples[0];
430
+ }
431
+ else {
432
+ let exampleCounter = 1;
433
+ mediaType.examples = parameterExamples.reduce((acc, ex, currentIndex) => {
434
+ const exampleLabel = parameterExampleLabels?.[currentIndex];
435
+ return { ...acc, [exampleLabel === undefined ? `Example ${exampleCounter++}` : exampleLabel]: { value: ex } };
436
+ }, {});
437
+ }
438
+ return mediaType;
439
+ }
440
+ buildQueriesParameter(source) {
441
+ if (source.type.dataType === 'refObject' || source.type.dataType === 'nestedObjectLiteral') {
442
+ const properties = source.type.properties;
443
+ return properties.map(property => this.buildParameter(this.queriesPropertyToQueryParameter(property)));
444
+ }
445
+ throw new Error(`Queries '${source.name}' parameter must be an object.`);
446
+ }
447
+ buildParameter(source) {
448
+ const parameter = {
449
+ description: source.description,
450
+ in: source.in,
451
+ name: source.name,
452
+ required: this.isRequiredWithoutDefault(source),
453
+ schema: {
454
+ default: source.default,
455
+ format: undefined,
456
+ },
457
+ };
458
+ if (source.deprecated) {
459
+ parameter.deprecated = true;
460
+ }
461
+ const parameterType = this.getSwaggerType(source.type);
462
+ if (parameterType.format) {
463
+ parameter.schema.format = this.throwIfNotDataFormat(parameterType.format);
464
+ }
465
+ if (parameterType.$ref) {
466
+ parameter.schema = parameterType;
467
+ return Object.assign(parameter, this.buildExamples(source));
468
+ }
469
+ const validatorObjs = {};
470
+ Object.keys(source.validators)
471
+ .filter(validatorUtils_1.shouldIncludeValidatorInSchema)
472
+ .forEach(key => {
473
+ validatorObjs[key] = source.validators[key].value;
474
+ });
475
+ if (source.type.dataType === 'any') {
476
+ parameter.schema.type = 'string';
477
+ }
478
+ else {
479
+ if (parameterType.type) {
480
+ parameter.schema.type = this.throwIfNotDataType(parameterType.type);
481
+ }
482
+ parameter.schema.items = parameterType.items;
483
+ parameter.schema.enum = parameterType.enum;
484
+ }
485
+ parameter.schema = Object.assign({}, parameter.schema, validatorObjs);
486
+ return Object.assign(parameter, this.buildExamples(source));
487
+ }
488
+ buildExamples(source) {
489
+ const { example: parameterExamples, exampleLabels } = source;
490
+ if (parameterExamples === undefined) {
491
+ return { example: undefined };
492
+ }
493
+ if (parameterExamples.length === 1) {
494
+ return { example: parameterExamples[0] };
495
+ }
496
+ let exampleCounter = 1;
497
+ const examples = parameterExamples.reduce((acc, ex, idx) => {
498
+ const label = exampleLabels?.[idx];
499
+ const name = label ?? `Example ${exampleCounter++}`;
500
+ acc[name] = { value: ex };
501
+ return acc;
502
+ }, {});
503
+ return { examples };
504
+ }
505
+ buildProperties(source) {
506
+ const properties = {};
507
+ source.forEach(property => {
508
+ let swaggerType = this.getSwaggerType(property.type);
509
+ const format = property.format;
510
+ swaggerType.description = property.description;
511
+ swaggerType.example = property.example;
512
+ swaggerType.format = format || swaggerType.format;
513
+ if (!swaggerType.$ref) {
514
+ swaggerType.default = property.default;
515
+ Object.keys(property.validators)
516
+ .filter(validatorUtils_1.shouldIncludeValidatorInSchema)
517
+ .forEach(key => {
518
+ swaggerType = { ...swaggerType, [key]: property.validators[key].value };
519
+ });
520
+ }
521
+ if (property.deprecated) {
522
+ swaggerType.deprecated = true;
523
+ }
524
+ if (property.title) {
525
+ swaggerType.title = property.title;
526
+ }
527
+ if (property.extensions) {
528
+ property.extensions.forEach(property => {
529
+ swaggerType[property.key] = property.value;
530
+ });
531
+ }
532
+ properties[property.name] = swaggerType;
533
+ });
534
+ return properties;
535
+ }
536
+ getSwaggerTypeForReferenceType(referenceType) {
537
+ return { $ref: `#/components/schemas/${encodeURIComponent(referenceType.refName)}` };
538
+ }
539
+ getSwaggerTypeForPrimitiveType(dataType) {
540
+ if (dataType === 'any') {
541
+ // Setting additionalProperties causes issues with code generators for OpenAPI 3
542
+ // Therefore, we avoid setting it explicitly (since it's the implicit default already)
543
+ return {};
544
+ }
545
+ else if (dataType === 'file') {
546
+ return { type: 'string', format: 'binary' };
547
+ }
548
+ return super.getSwaggerTypeForPrimitiveType(dataType);
549
+ }
550
+ isNull(type) {
551
+ return type.dataType === 'enum' && type.enums.length === 1 && type.enums[0] === null;
552
+ }
553
+ // Join disparate enums with the same type into one.
554
+ //
555
+ // grouping enums is helpful because it makes the spec more readable and it
556
+ // bypasses a failure in openapi-generator caused by using anyOf with
557
+ // duplicate types.
558
+ groupEnums(types) {
559
+ const returnTypes = [];
560
+ const enumValuesByType = {};
561
+ for (const type of types) {
562
+ if (type.enum && type.type) {
563
+ for (const enumValue of type.enum) {
564
+ if (!enumValuesByType[type.type]) {
565
+ enumValuesByType[type.type] = {};
566
+ }
567
+ enumValuesByType[type.type][String(enumValue)] = enumValue;
568
+ }
569
+ }
570
+ // preserve non-enum types
571
+ else {
572
+ returnTypes.push(type);
573
+ }
574
+ }
575
+ Object.keys(enumValuesByType).forEach(dataType => returnTypes.push({
576
+ type: dataType,
577
+ enum: Object.values(enumValuesByType[dataType]),
578
+ }));
579
+ return returnTypes;
580
+ }
581
+ removeDuplicateSwaggerTypes(types) {
582
+ if (types.length === 1) {
583
+ return types;
584
+ }
585
+ else {
586
+ const typesSet = new Set();
587
+ for (const type of types) {
588
+ typesSet.add(JSON.stringify(type));
589
+ }
590
+ return Array.from(typesSet).map(typeString => JSON.parse(typeString));
591
+ }
592
+ }
593
+ getSwaggerTypeForUnionType(type, title) {
594
+ // Filter out nulls and undefineds
595
+ const actualSwaggerTypes = this.removeDuplicateSwaggerTypes(this.groupEnums(type.types
596
+ .filter(x => !this.isNull(x))
597
+ .filter(x => x.dataType !== 'undefined')
598
+ .map(x => this.getSwaggerType(x))));
599
+ const nullable = type.types.some(x => this.isNull(x));
600
+ if (nullable) {
601
+ if (actualSwaggerTypes.length === 1) {
602
+ const [swaggerType] = actualSwaggerTypes;
603
+ // for ref union with null, use an allOf with a single
604
+ // element since you can't attach nullable directly to a ref.
605
+ // https://swagger.io/docs/specification/using-ref/#syntax
606
+ if (swaggerType.$ref) {
607
+ return { allOf: [swaggerType], nullable };
608
+ }
609
+ // Note that null must be explicitly included in the list of enum values. Using nullable: true alone is not enough here.
610
+ // https://swagger.io/docs/specification/data-models/enums/
611
+ if (swaggerType.enum) {
612
+ swaggerType.enum.push(null);
613
+ }
614
+ return { ...(title && { title }), ...swaggerType, nullable };
615
+ }
616
+ else {
617
+ return { ...(title && { title }), anyOf: actualSwaggerTypes, nullable };
618
+ }
619
+ }
620
+ else {
621
+ if (actualSwaggerTypes.length === 1) {
622
+ return { ...(title && { title }), ...actualSwaggerTypes[0] };
623
+ }
624
+ else {
625
+ return { ...(title && { title }), anyOf: actualSwaggerTypes };
626
+ }
627
+ }
628
+ }
629
+ getSwaggerTypeForIntersectionType(type, title) {
630
+ return { allOf: type.types.map(x => this.getSwaggerType(x)), ...(title && { title }) };
631
+ }
632
+ getSwaggerTypeForEnumType(enumType, title) {
633
+ const types = this.determineTypesUsedInEnum(enumType.enums);
634
+ if (types.size === 1) {
635
+ const type = types.values().next().value;
636
+ const nullable = enumType.enums.includes(null) ? true : false;
637
+ return { ...(title && { title }), type, enum: enumType.enums.map(member => (0, swaggerUtils_1.getValue)(type, member)), nullable };
638
+ }
639
+ else {
640
+ const valuesDelimited = Array.from(types).join(',');
641
+ throw new Error(`Enums can only have string or number values, but enum had ${valuesDelimited}`);
642
+ }
643
+ }
644
+ }
645
+ exports.SpecGenerator3 = SpecGenerator3;
646
+ //# sourceMappingURL=specGenerator3.js.map