nestjs-openapi-parser 0.0.1
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 +213 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +54 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +5 -0
- package/dist/config/defaults.js +14 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +24 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.js +160 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +139 -0
- package/dist/config/types.js +8 -0
- package/dist/config/types.js.map +1 -0
- package/dist/lib.d.ts +12 -0
- package/dist/lib.js +22 -0
- package/dist/lib.js.map +1 -0
- package/dist/parser/ast-index.d.ts +38 -0
- package/dist/parser/ast-index.js +124 -0
- package/dist/parser/ast-index.js.map +1 -0
- package/dist/parser/index.d.ts +20 -0
- package/dist/parser/index.js +95 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/path-builder.d.ts +87 -0
- package/dist/parser/path-builder.js +464 -0
- package/dist/parser/path-builder.js.map +1 -0
- package/dist/parser/schema-builder.d.ts +70 -0
- package/dist/parser/schema-builder.js +355 -0
- package/dist/parser/schema-builder.js.map +1 -0
- package/dist/parser/tags.d.ts +61 -0
- package/dist/parser/tags.js +163 -0
- package/dist/parser/tags.js.map +1 -0
- package/dist/types/openapi.d.ts +42 -0
- package/dist/types/openapi.js +3 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/validate.d.ts +13 -0
- package/dist/validate.js +30 -0
- package/dist/validate.js.map +1 -0
- package/docs/configuration.md +195 -0
- package/docs/library-usage.md +40 -0
- package/docs/parser.md +148 -0
- package/package.json +54 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PathBuilder = void 0;
|
|
4
|
+
const ts_morph_1 = require("ts-morph");
|
|
5
|
+
const ast_index_1 = require("./ast-index");
|
|
6
|
+
const tags_1 = require("./tags");
|
|
7
|
+
const HTTP_METHODS = {
|
|
8
|
+
Get: 'get',
|
|
9
|
+
Post: 'post',
|
|
10
|
+
Put: 'put',
|
|
11
|
+
Delete: 'delete',
|
|
12
|
+
Patch: 'patch',
|
|
13
|
+
};
|
|
14
|
+
// NestJS's `HttpStatus` enum (from `@nestjs/common`) member name → numeric code.
|
|
15
|
+
// Used to resolve `@HttpCode(HttpStatus.NO_CONTENT)` statically, since we can't
|
|
16
|
+
// import `@nestjs/common` to read the value at runtime.
|
|
17
|
+
const HTTP_STATUS_CODES = {
|
|
18
|
+
CONTINUE: 100,
|
|
19
|
+
SWITCHING_PROTOCOLS: 101,
|
|
20
|
+
PROCESSING: 102,
|
|
21
|
+
EARLYHINTS: 103,
|
|
22
|
+
OK: 200,
|
|
23
|
+
CREATED: 201,
|
|
24
|
+
ACCEPTED: 202,
|
|
25
|
+
NON_AUTHORITATIVE_INFORMATION: 203,
|
|
26
|
+
NO_CONTENT: 204,
|
|
27
|
+
RESET_CONTENT: 205,
|
|
28
|
+
PARTIAL_CONTENT: 206,
|
|
29
|
+
AMBIGUOUS: 300,
|
|
30
|
+
MOVED_PERMANENTLY: 301,
|
|
31
|
+
FOUND: 302,
|
|
32
|
+
SEE_OTHER: 303,
|
|
33
|
+
NOT_MODIFIED: 304,
|
|
34
|
+
TEMPORARY_REDIRECT: 307,
|
|
35
|
+
PERMANENT_REDIRECT: 308,
|
|
36
|
+
BAD_REQUEST: 400,
|
|
37
|
+
UNAUTHORIZED: 401,
|
|
38
|
+
PAYMENT_REQUIRED: 402,
|
|
39
|
+
FORBIDDEN: 403,
|
|
40
|
+
NOT_FOUND: 404,
|
|
41
|
+
METHOD_NOT_ALLOWED: 405,
|
|
42
|
+
NOT_ACCEPTABLE: 406,
|
|
43
|
+
PROXY_AUTHENTICATION_REQUIRED: 407,
|
|
44
|
+
REQUEST_TIMEOUT: 408,
|
|
45
|
+
CONFLICT: 409,
|
|
46
|
+
GONE: 410,
|
|
47
|
+
LENGTH_REQUIRED: 411,
|
|
48
|
+
PRECONDITION_FAILED: 412,
|
|
49
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
50
|
+
URI_TOO_LONG: 414,
|
|
51
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
52
|
+
REQUESTED_RANGE_NOT_SATISFIABLE: 416,
|
|
53
|
+
EXPECTATION_FAILED: 417,
|
|
54
|
+
I_AM_A_TEAPOT: 418,
|
|
55
|
+
MISDIRECTED: 421,
|
|
56
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
57
|
+
FAILED_DEPENDENCY: 424,
|
|
58
|
+
PRECONDITION_REQUIRED: 428,
|
|
59
|
+
TOO_MANY_REQUESTS: 429,
|
|
60
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
61
|
+
NOT_IMPLEMENTED: 501,
|
|
62
|
+
BAD_GATEWAY: 502,
|
|
63
|
+
SERVICE_UNAVAILABLE: 503,
|
|
64
|
+
GATEWAY_TIMEOUT: 504,
|
|
65
|
+
HTTP_VERSION_NOT_SUPPORTED: 505,
|
|
66
|
+
};
|
|
67
|
+
/** Humanize a method name: split camelCase/PascalCase boundaries and capitalize. */
|
|
68
|
+
function humanizeMethodName(name) {
|
|
69
|
+
const spaced = name
|
|
70
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
71
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
72
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Converts NestJS controllers into the OpenAPI `paths` object by static analysis:
|
|
76
|
+
* `@Controller` + HTTP route decorators, `@Param/@Query/@Body/@Headers`, method
|
|
77
|
+
* return types, and pluggable hooks for response envelope and security.
|
|
78
|
+
*/
|
|
79
|
+
class PathBuilder {
|
|
80
|
+
index;
|
|
81
|
+
schemaBuilder;
|
|
82
|
+
paths = {};
|
|
83
|
+
usedOperationIds = new Set();
|
|
84
|
+
tags = new Map();
|
|
85
|
+
/** Distinct method-level `@Tag` names seen, declared in root after controllers. */
|
|
86
|
+
methodTagNames = new Set();
|
|
87
|
+
globalPrefix;
|
|
88
|
+
hooks;
|
|
89
|
+
registeredSchemes;
|
|
90
|
+
activeScopes;
|
|
91
|
+
knownScopes;
|
|
92
|
+
constructor(index, schemaBuilder, options = {}) {
|
|
93
|
+
this.index = index;
|
|
94
|
+
this.schemaBuilder = schemaBuilder;
|
|
95
|
+
this.globalPrefix = options.globalPrefix ?? '';
|
|
96
|
+
this.hooks = options.hooks ?? {};
|
|
97
|
+
this.registeredSchemes = options.registeredSchemes ?? [];
|
|
98
|
+
this.activeScopes = options.activeScopes ?? new Set();
|
|
99
|
+
this.knownScopes = options.knownScopes;
|
|
100
|
+
}
|
|
101
|
+
build() {
|
|
102
|
+
for (const controller of this.index.getControllers()) {
|
|
103
|
+
this.processController(controller);
|
|
104
|
+
}
|
|
105
|
+
// Declare any method-level @Tag name no controller already declared, so the
|
|
106
|
+
// operation's tag isn't dangling. Done after all controllers so a controller's
|
|
107
|
+
// description always wins over a description-less method-tag placeholder.
|
|
108
|
+
for (const name of this.methodTagNames) {
|
|
109
|
+
if (!this.tags.has(name))
|
|
110
|
+
this.tags.set(name, undefined);
|
|
111
|
+
}
|
|
112
|
+
return this.paths;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Root `tags[]` entries: one per distinct tag name in play. Controller tags
|
|
116
|
+
* (name from `@Tag`/the default-tag hook, description from the class JSDoc;
|
|
117
|
+
* first controller wins on a shared name) come first, then any method-level
|
|
118
|
+
* `@Tag` name a controller didn't already declare — description-less, since a
|
|
119
|
+
* method's JSDoc is its operation description, not a tag description.
|
|
120
|
+
*/
|
|
121
|
+
getTags() {
|
|
122
|
+
return [...this.tags.entries()].map(([name, description]) => description ? { name, description } : { name });
|
|
123
|
+
}
|
|
124
|
+
processController(controller) {
|
|
125
|
+
if (!(0, tags_1.isVisible)((0, tags_1.getScopes)((0, tags_1.getTags)(controller)), this.activeScopes))
|
|
126
|
+
return;
|
|
127
|
+
// @Controller and the route decorators each accept `string | string[]`; an
|
|
128
|
+
// array prefix/path means the handler is mapped to several routes at once.
|
|
129
|
+
const basePathArgs = this.stringArgs(controller.getDecorator('Controller'), 0);
|
|
130
|
+
const basePaths = basePathArgs.length ? basePathArgs : [''];
|
|
131
|
+
const controllerTagBag = (0, tags_1.getTags)(controller);
|
|
132
|
+
const tag = controllerTagBag.Tag?.[0] ?? (this.hooks.controllerTag ?? this.defaultTag)(controller);
|
|
133
|
+
if (!this.tags.has(tag)) {
|
|
134
|
+
const rawDesc = controller.getJsDocs()[0]?.getCommentText();
|
|
135
|
+
const desc = rawDesc
|
|
136
|
+
? (0, tags_1.filterScopedComments)(rawDesc, this.activeScopes, {
|
|
137
|
+
itemPath: `${controller.getName() ?? '<anon>'} (tag)`,
|
|
138
|
+
knownScopes: this.knownScopes,
|
|
139
|
+
})
|
|
140
|
+
: undefined;
|
|
141
|
+
this.tags.set(tag, desc || undefined);
|
|
142
|
+
}
|
|
143
|
+
for (const method of controller.getInstanceMethods()) {
|
|
144
|
+
if (!(0, tags_1.isVisible)((0, tags_1.getScopes)((0, tags_1.getTags)(method)), this.activeScopes))
|
|
145
|
+
continue;
|
|
146
|
+
const httpDecorator = method.getDecorators().find((d) => HTTP_METHODS[d.getName()]);
|
|
147
|
+
if (!httpDecorator)
|
|
148
|
+
continue;
|
|
149
|
+
const httpMethod = HTTP_METHODS[httpDecorator.getName()];
|
|
150
|
+
const routeArgs = this.stringArgs(httpDecorator, 0);
|
|
151
|
+
const routePaths = routeArgs.length ? routeArgs : [''];
|
|
152
|
+
const methodTag = (0, tags_1.getTags)(method).Tag?.[0] ?? tag;
|
|
153
|
+
// One operation per (controller prefix × route path × optional-param
|
|
154
|
+
// expansion) combination. Each gets its own operationId (uniqueOperationId
|
|
155
|
+
// de-dups) and its own path params, since placeholders can differ per path.
|
|
156
|
+
for (const base of basePaths) {
|
|
157
|
+
for (const route of routePaths) {
|
|
158
|
+
const rawPath = this.joinPath(this.globalPrefix, base, route);
|
|
159
|
+
for (const fullPath of this.expandRoutePaths(rawPath, controller, method)) {
|
|
160
|
+
this.methodTagNames.add(methodTag);
|
|
161
|
+
const templateParams = this.pathParamNames(fullPath);
|
|
162
|
+
const operation = this.buildOperation(controller, method, httpMethod, methodTag, templateParams);
|
|
163
|
+
this.paths[fullPath] ??= {};
|
|
164
|
+
this.paths[fullPath][httpMethod] = operation;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
buildOperation(controller, method, httpMethod, tag, pathParamNames) {
|
|
171
|
+
const operation = {
|
|
172
|
+
operationId: this.uniqueOperationId(tag, method.getName()),
|
|
173
|
+
tags: [tag],
|
|
174
|
+
};
|
|
175
|
+
const summary = this.resolveSummary(controller, method, httpMethod);
|
|
176
|
+
if (summary)
|
|
177
|
+
operation.summary = summary;
|
|
178
|
+
const rawDesc = method.getJsDocs()[0]?.getCommentText();
|
|
179
|
+
const desc = rawDesc
|
|
180
|
+
? (0, tags_1.filterScopedComments)(rawDesc, this.activeScopes, {
|
|
181
|
+
itemPath: `${controller.getName() ?? '<anon>'}.${method.getName()}`,
|
|
182
|
+
knownScopes: this.knownScopes,
|
|
183
|
+
})
|
|
184
|
+
: undefined;
|
|
185
|
+
if (desc)
|
|
186
|
+
operation.description = desc;
|
|
187
|
+
// Collect explicit @Param('name') schemas keyed by name, plus query/header
|
|
188
|
+
// params in declaration order. Path params are emitted from the route
|
|
189
|
+
// template below, not from this loop, so the spec can never reference a
|
|
190
|
+
// `{param}` that has no parameter object.
|
|
191
|
+
const explicitPathParams = new Map();
|
|
192
|
+
const otherParameters = [];
|
|
193
|
+
let requestBody;
|
|
194
|
+
for (const param of method.getParameters()) {
|
|
195
|
+
const decorator = param
|
|
196
|
+
.getDecorators()
|
|
197
|
+
.find((d) => ['Param', 'Query', 'Body', 'Headers'].includes(d.getName()));
|
|
198
|
+
if (!decorator)
|
|
199
|
+
continue;
|
|
200
|
+
switch (decorator.getName()) {
|
|
201
|
+
case 'Param': {
|
|
202
|
+
const name = this.stringArg(decorator, 0);
|
|
203
|
+
// A @Param('name') whose name isn't in the route template can't be a
|
|
204
|
+
// valid path parameter, so it's recorded here and only used if the
|
|
205
|
+
// template actually declares it.
|
|
206
|
+
if (name)
|
|
207
|
+
explicitPathParams.set(name, this.paramSchema(decorator, param));
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case 'Query':
|
|
211
|
+
otherParameters.push(...this.buildQueryParameters(decorator, param));
|
|
212
|
+
break;
|
|
213
|
+
case 'Headers': {
|
|
214
|
+
const headerName = this.stringArg(decorator, 0);
|
|
215
|
+
if (headerName) {
|
|
216
|
+
otherParameters.push({
|
|
217
|
+
name: headerName,
|
|
218
|
+
in: 'header',
|
|
219
|
+
required: false,
|
|
220
|
+
schema: { type: 'string' },
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
case 'Body':
|
|
226
|
+
requestBody = this.buildRequestBody(param);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Every `{param}` in the route template must have a path-parameter entry, in
|
|
231
|
+
// template order — even when the handler never binds it with @Param('name')
|
|
232
|
+
// (e.g. it uses `@Param() all`, `@Req()`, or the name simply doesn't match).
|
|
233
|
+
// Such placeholders default to `string`.
|
|
234
|
+
const parameters = pathParamNames.map((name) => ({
|
|
235
|
+
name,
|
|
236
|
+
in: 'path',
|
|
237
|
+
required: true,
|
|
238
|
+
schema: explicitPathParams.get(name) ?? { type: 'string' },
|
|
239
|
+
}));
|
|
240
|
+
parameters.push(...otherParameters);
|
|
241
|
+
if (parameters.length)
|
|
242
|
+
operation.parameters = parameters;
|
|
243
|
+
if (requestBody)
|
|
244
|
+
operation.requestBody = requestBody;
|
|
245
|
+
operation.responses = this.buildResponses(method, httpMethod);
|
|
246
|
+
const security = this.buildSecurity(controller, method);
|
|
247
|
+
if (security !== undefined)
|
|
248
|
+
operation.security = security;
|
|
249
|
+
return operation;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The operation `summary`: a method-level `@Name` JSDoc tag (highest priority),
|
|
253
|
+
* else the `endpointSummary` hook when it returns a value, else the default —
|
|
254
|
+
* the method name humanized.
|
|
255
|
+
*/
|
|
256
|
+
resolveSummary(controller, method, httpMethod) {
|
|
257
|
+
const named = (0, tags_1.getTags)(method).Name?.[0];
|
|
258
|
+
if (named)
|
|
259
|
+
return named;
|
|
260
|
+
const defaultSummary = humanizeMethodName(method.getName());
|
|
261
|
+
return (this.hooks.endpointSummary?.({ controller, method, httpMethod, defaultSummary }) ??
|
|
262
|
+
defaultSummary);
|
|
263
|
+
}
|
|
264
|
+
buildQueryParameters(decorator, param) {
|
|
265
|
+
const argName = this.stringArg(decorator, 0);
|
|
266
|
+
if (argName) {
|
|
267
|
+
return [
|
|
268
|
+
{ name: argName, in: 'query', required: false, schema: this.paramSchema(decorator, param) },
|
|
269
|
+
];
|
|
270
|
+
}
|
|
271
|
+
// `@Query() dto: SomeDTO` -> expand DTO properties into individual query params.
|
|
272
|
+
const className = ast_index_1.AstIndex.symbolName(param.getType());
|
|
273
|
+
const clazz = className ? this.index.getClass(className) : undefined;
|
|
274
|
+
if (!clazz)
|
|
275
|
+
return [];
|
|
276
|
+
const members = this.schemaBuilder.buildMembers(clazz);
|
|
277
|
+
return Object.entries(members.properties).map(([name, schema]) => ({
|
|
278
|
+
name,
|
|
279
|
+
in: 'query',
|
|
280
|
+
required: members.required.includes(name),
|
|
281
|
+
schema,
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
buildRequestBody(param) {
|
|
285
|
+
const schema = this.schemaBuilder.typeToSchema(param.getType());
|
|
286
|
+
return { required: true, content: { 'application/json': { schema } } };
|
|
287
|
+
}
|
|
288
|
+
buildResponses(method, httpMethod) {
|
|
289
|
+
let returnType = method.getReturnType();
|
|
290
|
+
if (ast_index_1.AstIndex.symbolName(returnType) === 'Promise') {
|
|
291
|
+
const args = returnType.getTypeArguments();
|
|
292
|
+
if (args.length)
|
|
293
|
+
returnType = args[0];
|
|
294
|
+
}
|
|
295
|
+
const responseSchema = this.computeResponseSchema(method, returnType);
|
|
296
|
+
const status = this.responseStatus(method, httpMethod);
|
|
297
|
+
const response = { description: 'Successful response' };
|
|
298
|
+
if (responseSchema !== undefined) {
|
|
299
|
+
response.content = { 'application/json': { schema: responseSchema } };
|
|
300
|
+
}
|
|
301
|
+
return { [status]: response };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The success status code for an operation: an explicit `@HttpCode(...)` when
|
|
305
|
+
* present — either a numeric literal (`@HttpCode(204)`) or an `HttpStatus`
|
|
306
|
+
* member (`@HttpCode(HttpStatus.NO_CONTENT)`) — otherwise NestJS's default:
|
|
307
|
+
* 201 for POST, 200 for every other verb.
|
|
308
|
+
*/
|
|
309
|
+
responseStatus(method, httpMethod) {
|
|
310
|
+
const arg = method.getDecorator('HttpCode')?.getArguments()[0];
|
|
311
|
+
if (arg) {
|
|
312
|
+
if (ts_morph_1.Node.isNumericLiteral(arg))
|
|
313
|
+
return String(arg.getLiteralValue());
|
|
314
|
+
if (ts_morph_1.Node.isPropertyAccessExpression(arg)) {
|
|
315
|
+
const code = HTTP_STATUS_CODES[arg.getName()];
|
|
316
|
+
if (code !== undefined)
|
|
317
|
+
return String(code);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return httpMethod === 'post' ? '201' : '200';
|
|
321
|
+
}
|
|
322
|
+
computeResponseSchema(method, returnType) {
|
|
323
|
+
const isEmpty = returnType.isVoid() || returnType.isUndefined();
|
|
324
|
+
if (!this.hooks.buildResponseSchema) {
|
|
325
|
+
return isEmpty ? undefined : this.schemaBuilder.typeToSchema(returnType);
|
|
326
|
+
}
|
|
327
|
+
return this.hooks.buildResponseSchema({
|
|
328
|
+
method,
|
|
329
|
+
returnType,
|
|
330
|
+
returnTypeName: ast_index_1.AstIndex.symbolName(returnType),
|
|
331
|
+
returnTypeArgs: returnType.getTypeArguments(),
|
|
332
|
+
defaultSchema: () => (isEmpty ? {} : this.schemaBuilder.typeToSchema(returnType)),
|
|
333
|
+
typeToSchema: (t) => this.schemaBuilder.typeToSchema(t),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
buildSecurity(controller, method) {
|
|
337
|
+
if (this.hooks.resolveSecurity) {
|
|
338
|
+
return this.hooks.resolveSecurity({
|
|
339
|
+
controller,
|
|
340
|
+
method,
|
|
341
|
+
registeredSchemes: this.registeredSchemes,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// Default: every registered scheme applies (logical OR).
|
|
345
|
+
if (this.registeredSchemes.length === 0)
|
|
346
|
+
return undefined;
|
|
347
|
+
return this.registeredSchemes.map((name) => ({ [name]: [] }));
|
|
348
|
+
}
|
|
349
|
+
paramSchema(decorator, param) {
|
|
350
|
+
const argsText = decorator
|
|
351
|
+
.getArguments()
|
|
352
|
+
.map((a) => a.getText())
|
|
353
|
+
.join(' ');
|
|
354
|
+
if (argsText.includes('ParseUUIDPipe'))
|
|
355
|
+
return { type: 'string', format: 'uuid' };
|
|
356
|
+
if (argsText.includes('ParseIntPipe'))
|
|
357
|
+
return { type: 'integer' };
|
|
358
|
+
if (argsText.includes('ParseBoolPipe'))
|
|
359
|
+
return { type: 'boolean' };
|
|
360
|
+
return this.schemaBuilder.typeToSchema(param.getType());
|
|
361
|
+
}
|
|
362
|
+
stringArg(decorator, index) {
|
|
363
|
+
if (!decorator)
|
|
364
|
+
return undefined;
|
|
365
|
+
const arg = decorator.getArguments()[index];
|
|
366
|
+
return arg && ts_morph_1.Node.isStringLiteral(arg) ? arg.getLiteralValue() : undefined;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Read a decorator argument that may be a string literal or an array of string
|
|
370
|
+
* literals (`@Get('a')` or `@Get(['a', 'b'])`), returning the distinct values.
|
|
371
|
+
* Non-string and dynamic entries are skipped; the result is empty when absent.
|
|
372
|
+
*/
|
|
373
|
+
stringArgs(decorator, index) {
|
|
374
|
+
if (!decorator)
|
|
375
|
+
return [];
|
|
376
|
+
const arg = decorator.getArguments()[index];
|
|
377
|
+
if (!arg)
|
|
378
|
+
return [];
|
|
379
|
+
let values = [];
|
|
380
|
+
if (ts_morph_1.Node.isStringLiteral(arg)) {
|
|
381
|
+
values = [arg.getLiteralValue()];
|
|
382
|
+
}
|
|
383
|
+
else if (ts_morph_1.Node.isArrayLiteralExpression(arg)) {
|
|
384
|
+
values = arg
|
|
385
|
+
.getElements()
|
|
386
|
+
.filter(ts_morph_1.Node.isStringLiteral)
|
|
387
|
+
.map((el) => el.getLiteralValue());
|
|
388
|
+
}
|
|
389
|
+
return [...new Set(values)];
|
|
390
|
+
}
|
|
391
|
+
defaultTag(controller) {
|
|
392
|
+
const base = (controller.getName() ?? 'Default').replace(/Controller$/, '');
|
|
393
|
+
return base.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
394
|
+
}
|
|
395
|
+
uniqueOperationId(tag, methodName) {
|
|
396
|
+
const base = `${tag.replace(/\s+/g, '')}_${methodName}`;
|
|
397
|
+
let id = base;
|
|
398
|
+
let counter = 2;
|
|
399
|
+
while (this.usedOperationIds.has(id))
|
|
400
|
+
id = `${base}_${counter++}`;
|
|
401
|
+
this.usedOperationIds.add(id);
|
|
402
|
+
return id;
|
|
403
|
+
}
|
|
404
|
+
joinPath(...parts) {
|
|
405
|
+
return ('/' + parts.filter(Boolean).join('/')).replace(/\/{2,}/g, '/');
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Turn a raw NestJS route (with `:params`) into the OpenAPI path(s) it maps to.
|
|
409
|
+
*
|
|
410
|
+
* - An optional param (`:id?`) expands into the with/without pair, because
|
|
411
|
+
* OpenAPI path params are always required (`a/:id?` → `/a` and `/a/{id}`).
|
|
412
|
+
* - Constructs OpenAPI can't represent — inline regex (`:id(\d+)`), wildcards
|
|
413
|
+
* (`*`, `:splat*`), `+`/`*` modifiers, and more than one optional param —
|
|
414
|
+
* cause the route to be skipped with a warning.
|
|
415
|
+
*/
|
|
416
|
+
expandRoutePaths(rawPath, controller, method) {
|
|
417
|
+
const where = () => `${controller.getName() ?? '<anon>'}.${method.getName()}`;
|
|
418
|
+
const skip = (reason) => {
|
|
419
|
+
console.warn(`[nestparser] Skipping route "${rawPath}" (${where()}): ${reason}`);
|
|
420
|
+
return [];
|
|
421
|
+
};
|
|
422
|
+
if (/[*+()]/.test(rawPath)) {
|
|
423
|
+
return skip("unsupported path pattern (regex, wildcard or modifier). Only ':param' and optional ':param?' are handled.");
|
|
424
|
+
}
|
|
425
|
+
const segments = rawPath.split('/').filter(Boolean);
|
|
426
|
+
const optionalIdx = segments.flatMap((s, i) => (/^:[A-Za-z0-9_]+\?$/.test(s) ? [i] : []));
|
|
427
|
+
if (optionalIdx.length > 1) {
|
|
428
|
+
return skip("more than one optional ':param?' in a route is not supported.");
|
|
429
|
+
}
|
|
430
|
+
if (optionalIdx.length === 0) {
|
|
431
|
+
return [this.toOpenApiPath('/' + segments.join('/'))];
|
|
432
|
+
}
|
|
433
|
+
// Exactly one optional: emit the route without that segment, and with it
|
|
434
|
+
// present (the trailing `?` dropped so it's a normal required param).
|
|
435
|
+
const i = optionalIdx[0];
|
|
436
|
+
const without = segments.filter((_, idx) => idx !== i);
|
|
437
|
+
const present = segments.map((s, idx) => (idx === i ? s.slice(0, -1) : s));
|
|
438
|
+
return [
|
|
439
|
+
this.toOpenApiPath('/' + without.join('/')),
|
|
440
|
+
this.toOpenApiPath('/' + present.join('/')),
|
|
441
|
+
];
|
|
442
|
+
}
|
|
443
|
+
toOpenApiPath(routePath) {
|
|
444
|
+
let out = routePath.replace(/:([A-Za-z0-9_]+)/g, '{$1}');
|
|
445
|
+
if (out.length > 1 && out.endsWith('/'))
|
|
446
|
+
out = out.slice(0, -1);
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
/** Names of the `{param}` placeholders in an OpenAPI path, in order, deduped. */
|
|
450
|
+
pathParamNames(openApiPath) {
|
|
451
|
+
const names = [];
|
|
452
|
+
const seen = new Set();
|
|
453
|
+
for (const match of openApiPath.matchAll(/\{([^}]+)\}/g)) {
|
|
454
|
+
const name = match[1];
|
|
455
|
+
if (!seen.has(name)) {
|
|
456
|
+
seen.add(name);
|
|
457
|
+
names.push(name);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return names;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
exports.PathBuilder = PathBuilder;
|
|
464
|
+
//# sourceMappingURL=path-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-builder.js","sourceRoot":"","sources":["../../src/parser/path-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ClassDeclaration, Type } from 'ts-morph';
|
|
2
|
+
import type { OpenApiSchema } from '../types/openapi';
|
|
3
|
+
import { AstIndex } from './ast-index';
|
|
4
|
+
export interface SchemaBuilderOptions {
|
|
5
|
+
activeScopes?: Set<string>;
|
|
6
|
+
/** Scope vocabulary used to recognize `<scope>…</scope>` description fragments. */
|
|
7
|
+
knownScopes?: Set<string>;
|
|
8
|
+
}
|
|
9
|
+
export interface SchemaMembers {
|
|
10
|
+
properties: Record<string, OpenApiSchema>;
|
|
11
|
+
required: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Converts TypeScript classes (entities & DTOs) into OpenAPI `components.schemas`.
|
|
15
|
+
* Handles:
|
|
16
|
+
* - required derived from `@IsOptional()` / `?` (configurable)
|
|
17
|
+
* - `@Exclude()` properties omitted (configurable)
|
|
18
|
+
* - union object types -> `oneOf`, string-literal unions -> enum
|
|
19
|
+
* - `PartialType / PickType / OmitType / IntersectionType` heritage
|
|
20
|
+
*/
|
|
21
|
+
export declare class SchemaBuilder {
|
|
22
|
+
private readonly index;
|
|
23
|
+
private readonly schemas;
|
|
24
|
+
private readonly pending;
|
|
25
|
+
private readonly done;
|
|
26
|
+
private readonly activeScopes;
|
|
27
|
+
private readonly knownScopes;
|
|
28
|
+
constructor(index: AstIndex, options?: SchemaBuilderOptions);
|
|
29
|
+
/** Register a reference to a class schema and return the `$ref` fragment. */
|
|
30
|
+
registerRef(name: string): OpenApiSchema;
|
|
31
|
+
/** Process the queue until no schema remains to build. */
|
|
32
|
+
build(): void;
|
|
33
|
+
getSchemas(): Record<string, OpenApiSchema>;
|
|
34
|
+
/** Public entry to map an arbitrary type to an OpenAPI schema fragment. */
|
|
35
|
+
typeToSchema(type: Type): OpenApiSchema;
|
|
36
|
+
/** Flatten a class's properties (own + inherited / mapped-type heritage). */
|
|
37
|
+
buildMembers(clazz: ClassDeclaration): SchemaMembers;
|
|
38
|
+
private buildOwnMembers;
|
|
39
|
+
/** Resolve `PartialType() / PickType() / OmitType() / IntersectionType()` heritage. */
|
|
40
|
+
private resolveHeritage;
|
|
41
|
+
private schemaForType;
|
|
42
|
+
/**
|
|
43
|
+
* Build the schema for a named TS enum, deriving `type` from the member
|
|
44
|
+
* values rather than assuming strings:
|
|
45
|
+
* - all strings -> `string`
|
|
46
|
+
* - all integers -> `integer`
|
|
47
|
+
* - all numbers (some non-integer) -> `number`
|
|
48
|
+
* - mixed string/number -> `type` omitted (no single OpenAPI type fits)
|
|
49
|
+
*/
|
|
50
|
+
private enumSchema;
|
|
51
|
+
/**
|
|
52
|
+
* Merge class-validator constraints from a property's decorators into its
|
|
53
|
+
* schema. Unknown decorators are ignored. A `$ref` is left untouched (its
|
|
54
|
+
* siblings are ignored by OpenAPI 3.0, so constraints don't belong on it).
|
|
55
|
+
*
|
|
56
|
+
* - `@Min/@Max` -> `minimum` / `maximum`
|
|
57
|
+
* - `@MinLength/@MaxLength` -> `minLength` / `maxLength`
|
|
58
|
+
* - `@Length(min, max)` -> `minLength` + `maxLength`
|
|
59
|
+
* - `@ArrayMinSize/@ArrayMaxSize` -> `minItems` / `maxItems`
|
|
60
|
+
* - `@IsEmail/@IsUrl/@IsUUID/@IsDateString` -> `format`
|
|
61
|
+
* - `@Matches(/re/)` -> `pattern`
|
|
62
|
+
* - `@IsInt` -> narrows `number` to `integer`
|
|
63
|
+
* - `@IsPositive/@IsNegative` -> exclusive `minimum` / `maximum` of 0
|
|
64
|
+
*/
|
|
65
|
+
private applyValidatorConstraints;
|
|
66
|
+
private mergeMembers;
|
|
67
|
+
private pick;
|
|
68
|
+
private omit;
|
|
69
|
+
private parseStringArray;
|
|
70
|
+
}
|