@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.
- package/README.MD +3 -0
- package/dist/cli.d.ts +44 -0
- package/dist/cli.js +356 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/metadataGeneration/controllerGenerator.d.ts +30 -0
- package/dist/metadataGeneration/controllerGenerator.js +229 -0
- package/dist/metadataGeneration/controllerGenerator.js.map +1 -0
- package/dist/metadataGeneration/exceptions.d.ts +13 -0
- package/dist/metadataGeneration/exceptions.js +53 -0
- package/dist/metadataGeneration/exceptions.js.map +1 -0
- package/dist/metadataGeneration/extension.d.ts +5 -0
- package/dist/metadataGeneration/extension.js +85 -0
- package/dist/metadataGeneration/extension.js.map +1 -0
- package/dist/metadataGeneration/initializer-value.d.ts +6 -0
- package/dist/metadataGeneration/initializer-value.js +154 -0
- package/dist/metadataGeneration/initializer-value.js.map +1 -0
- package/dist/metadataGeneration/metadataGenerator.d.ts +29 -0
- package/dist/metadataGeneration/metadataGenerator.js +220 -0
- package/dist/metadataGeneration/metadataGenerator.js.map +1 -0
- package/dist/metadataGeneration/methodGenerator.d.ts +45 -0
- package/dist/metadataGeneration/methodGenerator.js +367 -0
- package/dist/metadataGeneration/methodGenerator.js.map +1 -0
- package/dist/metadataGeneration/parameterGenerator.d.ts +33 -0
- package/dist/metadataGeneration/parameterGenerator.js +552 -0
- package/dist/metadataGeneration/parameterGenerator.js.map +1 -0
- package/dist/metadataGeneration/transformer/dateTransformer.d.ts +6 -0
- package/dist/metadataGeneration/transformer/dateTransformer.js +28 -0
- package/dist/metadataGeneration/transformer/dateTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/enumTransformer.d.ts +12 -0
- package/dist/metadataGeneration/transformer/enumTransformer.js +75 -0
- package/dist/metadataGeneration/transformer/enumTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/primitiveTransformer.d.ts +11 -0
- package/dist/metadataGeneration/transformer/primitiveTransformer.js +70 -0
- package/dist/metadataGeneration/transformer/primitiveTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/propertyTransformer.d.ts +12 -0
- package/dist/metadataGeneration/transformer/propertyTransformer.js +101 -0
- package/dist/metadataGeneration/transformer/propertyTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/referenceTransformer.d.ts +10 -0
- package/dist/metadataGeneration/transformer/referenceTransformer.js +81 -0
- package/dist/metadataGeneration/transformer/referenceTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/transformer.d.ts +9 -0
- package/dist/metadataGeneration/transformer/transformer.js +39 -0
- package/dist/metadataGeneration/transformer/transformer.js.map +1 -0
- package/dist/metadataGeneration/typeResolver.d.ts +52 -0
- package/dist/metadataGeneration/typeResolver.js +1202 -0
- package/dist/metadataGeneration/typeResolver.js.map +1 -0
- package/dist/module/generate-routes.d.ts +9 -0
- package/dist/module/generate-routes.js +90 -0
- package/dist/module/generate-routes.js.map +1 -0
- package/dist/module/generate-spec.d.ts +9 -0
- package/dist/module/generate-spec.js +79 -0
- package/dist/module/generate-spec.js.map +1 -0
- package/dist/routeGeneration/defaultRouteGenerator.d.ts +12 -0
- package/dist/routeGeneration/defaultRouteGenerator.js +119 -0
- package/dist/routeGeneration/defaultRouteGenerator.js.map +1 -0
- package/dist/routeGeneration/routeGenerator.d.ts +56 -0
- package/dist/routeGeneration/routeGenerator.js +257 -0
- package/dist/routeGeneration/routeGenerator.js.map +1 -0
- package/dist/routeGeneration/templates/express.hbs +221 -0
- package/dist/routeGeneration/templates/hapi.hbs +267 -0
- package/dist/routeGeneration/templates/koa.hbs +218 -0
- package/dist/swagger/specGenerator.d.ts +33 -0
- package/dist/swagger/specGenerator.js +253 -0
- package/dist/swagger/specGenerator.js.map +1 -0
- package/dist/swagger/specGenerator2.d.ts +27 -0
- package/dist/swagger/specGenerator2.js +476 -0
- package/dist/swagger/specGenerator2.js.map +1 -0
- package/dist/swagger/specGenerator3.d.ts +158 -0
- package/dist/swagger/specGenerator3.js +646 -0
- package/dist/swagger/specGenerator3.js.map +1 -0
- package/dist/swagger/specGenerator31.d.ts +24 -0
- package/dist/swagger/specGenerator31.js +75 -0
- package/dist/swagger/specGenerator31.js.map +1 -0
- package/dist/utils/decoratorUtils.d.ts +9 -0
- package/dist/utils/decoratorUtils.js +118 -0
- package/dist/utils/decoratorUtils.js.map +1 -0
- package/dist/utils/flowUtils.d.ts +1 -0
- package/dist/utils/flowUtils.js +8 -0
- package/dist/utils/flowUtils.js.map +1 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.js +55 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/genericTypeGuards.d.ts +1 -0
- package/dist/utils/genericTypeGuards.js +8 -0
- package/dist/utils/genericTypeGuards.js.map +1 -0
- package/dist/utils/headerTypeHelpers.d.ts +5 -0
- package/dist/utils/headerTypeHelpers.js +27 -0
- package/dist/utils/headerTypeHelpers.js.map +1 -0
- package/dist/utils/importClassesFromDirectories.d.ts +4 -0
- package/dist/utils/importClassesFromDirectories.js +20 -0
- package/dist/utils/importClassesFromDirectories.js.map +1 -0
- package/dist/utils/internalTypeGuards.d.ts +5 -0
- package/dist/utils/internalTypeGuards.js +66 -0
- package/dist/utils/internalTypeGuards.js.map +1 -0
- package/dist/utils/isVoidType.d.ts +2 -0
- package/dist/utils/isVoidType.js +16 -0
- package/dist/utils/isVoidType.js.map +1 -0
- package/dist/utils/jsDocUtils.d.ts +8 -0
- package/dist/utils/jsDocUtils.js +122 -0
- package/dist/utils/jsDocUtils.js.map +1 -0
- package/dist/utils/jsonUtils.d.ts +1 -0
- package/dist/utils/jsonUtils.js +12 -0
- package/dist/utils/jsonUtils.js.map +1 -0
- package/dist/utils/pathUtils.d.ts +9 -0
- package/dist/utils/pathUtils.js +37 -0
- package/dist/utils/pathUtils.js.map +1 -0
- package/dist/utils/specMerge.d.ts +2 -0
- package/dist/utils/specMerge.js +36 -0
- package/dist/utils/specMerge.js.map +1 -0
- package/dist/utils/swaggerUtils.d.ts +3 -0
- package/dist/utils/swaggerUtils.js +22 -0
- package/dist/utils/swaggerUtils.js.map +1 -0
- package/dist/utils/unspecifiedObject.d.ts +3 -0
- package/dist/utils/unspecifiedObject.js +3 -0
- package/dist/utils/unspecifiedObject.js.map +1 -0
- package/dist/utils/validatorUtils.d.ts +5 -0
- package/dist/utils/validatorUtils.js +241 -0
- package/dist/utils/validatorUtils.js.map +1 -0
- 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
|