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,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defineConfig = defineConfig;
|
|
4
|
+
/** Helper for `nestparser.config.ts` so users get full type-checking. */
|
|
5
|
+
function defineConfig(config) {
|
|
6
|
+
return config;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":";;AA8JA,oCAEC;AAHD,yEAAyE;AACzE,SAAgB,YAAY,CAAC,MAAwB;IACnD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { parseNestProject } from './parser';
|
|
2
|
+
export type { ParseNestProjectOptions } from './parser';
|
|
3
|
+
export { AstIndex, PathBuilder, SchemaBuilder } from './parser';
|
|
4
|
+
export { defineConfig } from './config/types';
|
|
5
|
+
export type { NestParserConfig, NestParserHooks, OpenApiConfig, ProjectConfig, ConventionsConfig, ResponseSchemaContext, SecurityContext, EndpointSummaryContext, ModelConstructor, } from './config/types';
|
|
6
|
+
export { loadConfig } from './config/loader';
|
|
7
|
+
export type { LoadConfigOptions, LoadedConfig } from './config/loader';
|
|
8
|
+
export { validateDocument } from './validate';
|
|
9
|
+
export type { ValidationResult } from './validate';
|
|
10
|
+
export { filterScopedComments, getScopes, getTags, isVisible, parseScopeList } from './parser/tags';
|
|
11
|
+
export type { TagBag } from './parser/tags';
|
|
12
|
+
export type { OpenApiDocument, OpenApiInfo, OpenApiServer, OpenApiSchema, OpenApiSecurityScheme, OpenApiSecurityRequirement, } from './types/openapi';
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseScopeList = exports.isVisible = exports.getTags = exports.getScopes = exports.filterScopedComments = exports.validateDocument = exports.loadConfig = exports.defineConfig = exports.SchemaBuilder = exports.PathBuilder = exports.AstIndex = exports.parseNestProject = void 0;
|
|
4
|
+
var parser_1 = require("./parser");
|
|
5
|
+
Object.defineProperty(exports, "parseNestProject", { enumerable: true, get: function () { return parser_1.parseNestProject; } });
|
|
6
|
+
var parser_2 = require("./parser");
|
|
7
|
+
Object.defineProperty(exports, "AstIndex", { enumerable: true, get: function () { return parser_2.AstIndex; } });
|
|
8
|
+
Object.defineProperty(exports, "PathBuilder", { enumerable: true, get: function () { return parser_2.PathBuilder; } });
|
|
9
|
+
Object.defineProperty(exports, "SchemaBuilder", { enumerable: true, get: function () { return parser_2.SchemaBuilder; } });
|
|
10
|
+
var types_1 = require("./config/types");
|
|
11
|
+
Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function () { return types_1.defineConfig; } });
|
|
12
|
+
var loader_1 = require("./config/loader");
|
|
13
|
+
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return loader_1.loadConfig; } });
|
|
14
|
+
var validate_1 = require("./validate");
|
|
15
|
+
Object.defineProperty(exports, "validateDocument", { enumerable: true, get: function () { return validate_1.validateDocument; } });
|
|
16
|
+
var tags_1 = require("./parser/tags");
|
|
17
|
+
Object.defineProperty(exports, "filterScopedComments", { enumerable: true, get: function () { return tags_1.filterScopedComments; } });
|
|
18
|
+
Object.defineProperty(exports, "getScopes", { enumerable: true, get: function () { return tags_1.getScopes; } });
|
|
19
|
+
Object.defineProperty(exports, "getTags", { enumerable: true, get: function () { return tags_1.getTags; } });
|
|
20
|
+
Object.defineProperty(exports, "isVisible", { enumerable: true, get: function () { return tags_1.isVisible; } });
|
|
21
|
+
Object.defineProperty(exports, "parseScopeList", { enumerable: true, get: function () { return tags_1.parseScopeList; } });
|
|
22
|
+
//# sourceMappingURL=lib.js.map
|
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":";;;AAAA,mCAA4C;AAAnC,0GAAA,gBAAgB,OAAA;AAEzB,mCAAgE;AAAvD,kGAAA,QAAQ,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,uGAAA,aAAa,OAAA;AAE7C,wCAA8C;AAArC,qGAAA,YAAY,OAAA;AAarB,0CAA6C;AAApC,oGAAA,UAAU,OAAA;AAGnB,uCAA8C;AAArC,4GAAA,gBAAgB,OAAA;AAGzB,sCAAoG;AAA3F,4GAAA,oBAAoB,OAAA;AAAE,iGAAA,SAAS,OAAA;AAAE,+FAAA,OAAO,OAAA;AAAE,iGAAA,SAAS,OAAA;AAAE,sGAAA,cAAc,OAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ClassDeclaration, ClassInstancePropertyTypes, Project, Type } from 'ts-morph';
|
|
2
|
+
import type { ConventionsConfig, ProjectConfig } from '../config/types';
|
|
3
|
+
export interface AstIndexOptions {
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
project?: ProjectConfig;
|
|
6
|
+
conventions?: ConventionsConfig;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Builds and indexes the TypeScript AST of the user's source tree with ts-morph
|
|
10
|
+
* so the OpenAPI generator can resolve classes (entities/DTOs), enums and
|
|
11
|
+
* controllers purely from source code.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AstIndex {
|
|
14
|
+
readonly project: Project;
|
|
15
|
+
private readonly classesMap;
|
|
16
|
+
private readonly enumsMap;
|
|
17
|
+
private readonly conventions;
|
|
18
|
+
constructor(options: AstIndexOptions);
|
|
19
|
+
private generateMaps;
|
|
20
|
+
getClass(name: string): ClassDeclaration | undefined;
|
|
21
|
+
hasClass(name: string): boolean;
|
|
22
|
+
hasEnum(name: string): boolean;
|
|
23
|
+
/** All classes decorated with `@Controller(...)`. */
|
|
24
|
+
getControllers(): ClassDeclaration[];
|
|
25
|
+
/**
|
|
26
|
+
* Every distinct `@Scope` value declared anywhere in the indexed source
|
|
27
|
+
* (classes, methods, properties). This is the scope *vocabulary* — used to
|
|
28
|
+
* tell genuine `<scope>…</scope>` description fragments apart from ordinary
|
|
29
|
+
* angle-bracket prose like `Array<string>` or `<id>`.
|
|
30
|
+
*/
|
|
31
|
+
getDeclaredScopes(): Set<string>;
|
|
32
|
+
/** Resolve the named enum's member values (string or numeric), or undefined if unknown. */
|
|
33
|
+
getEnumValues(name: string): (string | number)[] | undefined;
|
|
34
|
+
/** Symbol name of a type (e.g. `Date`, `App`, `ProductType`), if any. */
|
|
35
|
+
static symbolName(type: Type): string | undefined;
|
|
36
|
+
isOptionalProperty(prop: ClassInstancePropertyTypes): boolean;
|
|
37
|
+
get excludeDecorator(): string;
|
|
38
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AstIndex = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const ts_morph_1 = require("ts-morph");
|
|
10
|
+
const defaults_1 = require("../config/defaults");
|
|
11
|
+
const tags_1 = require("./tags");
|
|
12
|
+
/**
|
|
13
|
+
* Builds and indexes the TypeScript AST of the user's source tree with ts-morph
|
|
14
|
+
* so the OpenAPI generator can resolve classes (entities/DTOs), enums and
|
|
15
|
+
* controllers purely from source code.
|
|
16
|
+
*/
|
|
17
|
+
class AstIndex {
|
|
18
|
+
project;
|
|
19
|
+
classesMap = new Map();
|
|
20
|
+
enumsMap = new Map();
|
|
21
|
+
conventions;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
const projectCfg = { ...defaults_1.DEFAULT_PROJECT, ...options.project };
|
|
24
|
+
this.conventions = { ...defaults_1.DEFAULT_CONVENTIONS, ...options.conventions };
|
|
25
|
+
const tsConfigFilePath = node_path_1.default.isAbsolute(projectCfg.tsConfigFilePath)
|
|
26
|
+
? projectCfg.tsConfigFilePath
|
|
27
|
+
: node_path_1.default.resolve(options.projectRoot, projectCfg.tsConfigFilePath);
|
|
28
|
+
this.project = new ts_morph_1.Project({ tsConfigFilePath });
|
|
29
|
+
const rootDir = node_path_1.default.isAbsolute(projectCfg.rootDir)
|
|
30
|
+
? projectCfg.rootDir
|
|
31
|
+
: node_path_1.default.resolve(options.projectRoot, projectCfg.rootDir);
|
|
32
|
+
this.generateMaps(rootDir, projectCfg.excludeSuffixes);
|
|
33
|
+
}
|
|
34
|
+
generateMaps(folder, excludeSuffixes) {
|
|
35
|
+
if (!node_fs_1.default.existsSync(folder))
|
|
36
|
+
return;
|
|
37
|
+
// Sort entries by name so the traversal order — and therefore the order of
|
|
38
|
+
// paths, schemas and tags in the output — is identical across filesystems
|
|
39
|
+
// and platforms. `fs.readdirSync` order is not guaranteed (arbitrary on
|
|
40
|
+
// ext4/xfs), and `localeCompare` would reintroduce locale-dependent
|
|
41
|
+
// ordering, so compare raw strings by UTF-16 code unit.
|
|
42
|
+
const entries = node_fs_1.default
|
|
43
|
+
.readdirSync(folder, { withFileTypes: true })
|
|
44
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const full = node_path_1.default.join(folder, entry.name);
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
this.generateMaps(full, excludeSuffixes);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!entry.name.endsWith('.ts'))
|
|
52
|
+
continue;
|
|
53
|
+
if (excludeSuffixes.some((suffix) => entry.name.endsWith(suffix)))
|
|
54
|
+
continue;
|
|
55
|
+
const sourceFile = this.project.getSourceFile(full);
|
|
56
|
+
if (!sourceFile)
|
|
57
|
+
continue;
|
|
58
|
+
for (const clazz of sourceFile.getClasses()) {
|
|
59
|
+
const name = clazz.getName();
|
|
60
|
+
if (name)
|
|
61
|
+
this.classesMap.set(name, clazz);
|
|
62
|
+
}
|
|
63
|
+
for (const e of sourceFile.getEnums()) {
|
|
64
|
+
this.enumsMap.set(e.getName(), e);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
getClass(name) {
|
|
69
|
+
return this.classesMap.get(name);
|
|
70
|
+
}
|
|
71
|
+
hasClass(name) {
|
|
72
|
+
return this.classesMap.has(name);
|
|
73
|
+
}
|
|
74
|
+
hasEnum(name) {
|
|
75
|
+
return this.enumsMap.has(name);
|
|
76
|
+
}
|
|
77
|
+
/** All classes decorated with `@Controller(...)`. */
|
|
78
|
+
getControllers() {
|
|
79
|
+
return [...this.classesMap.values()].filter((c) => !!c.getDecorator('Controller'));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Every distinct `@Scope` value declared anywhere in the indexed source
|
|
83
|
+
* (classes, methods, properties). This is the scope *vocabulary* — used to
|
|
84
|
+
* tell genuine `<scope>…</scope>` description fragments apart from ordinary
|
|
85
|
+
* angle-bracket prose like `Array<string>` or `<id>`.
|
|
86
|
+
*/
|
|
87
|
+
getDeclaredScopes() {
|
|
88
|
+
const scopes = new Set();
|
|
89
|
+
for (const clazz of this.classesMap.values()) {
|
|
90
|
+
for (const s of (0, tags_1.getScopes)((0, tags_1.getTags)(clazz)))
|
|
91
|
+
scopes.add(s);
|
|
92
|
+
for (const method of clazz.getInstanceMethods()) {
|
|
93
|
+
for (const s of (0, tags_1.getScopes)((0, tags_1.getTags)(method)))
|
|
94
|
+
scopes.add(s);
|
|
95
|
+
}
|
|
96
|
+
for (const prop of clazz.getInstanceProperties()) {
|
|
97
|
+
for (const s of (0, tags_1.getScopes)((0, tags_1.getTags)(prop)))
|
|
98
|
+
scopes.add(s);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return scopes;
|
|
102
|
+
}
|
|
103
|
+
/** Resolve the named enum's member values (string or numeric), or undefined if unknown. */
|
|
104
|
+
getEnumValues(name) {
|
|
105
|
+
const e = this.enumsMap.get(name);
|
|
106
|
+
if (!e)
|
|
107
|
+
return undefined;
|
|
108
|
+
return e.getMembers().map((m) => m.getValue());
|
|
109
|
+
}
|
|
110
|
+
/** Symbol name of a type (e.g. `Date`, `App`, `ProductType`), if any. */
|
|
111
|
+
static symbolName(type) {
|
|
112
|
+
return type.getSymbol()?.getName() ?? type.getAliasSymbol()?.getName();
|
|
113
|
+
}
|
|
114
|
+
isOptionalProperty(prop) {
|
|
115
|
+
if (!ts_morph_1.Node.isPropertyDeclaration(prop))
|
|
116
|
+
return false;
|
|
117
|
+
return prop.hasQuestionToken() || !!prop.getDecorator(this.conventions.optionalDecorator);
|
|
118
|
+
}
|
|
119
|
+
get excludeDecorator() {
|
|
120
|
+
return this.conventions.excludeDecorator;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.AstIndex = AstIndex;
|
|
124
|
+
//# sourceMappingURL=ast-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-index.js","sourceRoot":"","sources":["../../src/parser/ast-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NestParserConfig } from '../config/types';
|
|
2
|
+
import type { OpenApiDocument } from '../types/openapi';
|
|
3
|
+
import { AstIndex } from './ast-index';
|
|
4
|
+
import { PathBuilder } from './path-builder';
|
|
5
|
+
import { SchemaBuilder } from './schema-builder';
|
|
6
|
+
export { AstIndex, PathBuilder, SchemaBuilder };
|
|
7
|
+
export interface ParseNestProjectOptions {
|
|
8
|
+
projectRoot: string;
|
|
9
|
+
config: NestParserConfig;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build an OpenAPI 3.0.3 document from a NestJS project's TypeScript source.
|
|
13
|
+
* Pure static analysis — no app boot, no decorator reflection.
|
|
14
|
+
*
|
|
15
|
+
* The produced document is validated against the OpenAPI 3.x schema before it is
|
|
16
|
+
* returned; an invalid document throws (it indicates a parser/config bug rather
|
|
17
|
+
* than something the caller should silently ship). This is why the function is
|
|
18
|
+
* async — schema validation runs through `@seriousme/openapi-schema-validator`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseNestProject(options: ParseNestProjectOptions): Promise<OpenApiDocument>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaBuilder = exports.PathBuilder = exports.AstIndex = void 0;
|
|
4
|
+
exports.parseNestProject = parseNestProject;
|
|
5
|
+
const validate_1 = require("../validate");
|
|
6
|
+
const ast_index_1 = require("./ast-index");
|
|
7
|
+
Object.defineProperty(exports, "AstIndex", { enumerable: true, get: function () { return ast_index_1.AstIndex; } });
|
|
8
|
+
const path_builder_1 = require("./path-builder");
|
|
9
|
+
Object.defineProperty(exports, "PathBuilder", { enumerable: true, get: function () { return path_builder_1.PathBuilder; } });
|
|
10
|
+
const schema_builder_1 = require("./schema-builder");
|
|
11
|
+
Object.defineProperty(exports, "SchemaBuilder", { enumerable: true, get: function () { return schema_builder_1.SchemaBuilder; } });
|
|
12
|
+
const tags_1 = require("./tags");
|
|
13
|
+
/**
|
|
14
|
+
* Build an OpenAPI 3.0.3 document from a NestJS project's TypeScript source.
|
|
15
|
+
* Pure static analysis — no app boot, no decorator reflection.
|
|
16
|
+
*
|
|
17
|
+
* The produced document is validated against the OpenAPI 3.x schema before it is
|
|
18
|
+
* returned; an invalid document throws (it indicates a parser/config bug rather
|
|
19
|
+
* than something the caller should silently ship). This is why the function is
|
|
20
|
+
* async — schema validation runs through `@seriousme/openapi-schema-validator`.
|
|
21
|
+
*/
|
|
22
|
+
async function parseNestProject(options) {
|
|
23
|
+
const { projectRoot, config } = options;
|
|
24
|
+
const index = new ast_index_1.AstIndex({
|
|
25
|
+
projectRoot,
|
|
26
|
+
project: config.project,
|
|
27
|
+
conventions: config.conventions,
|
|
28
|
+
});
|
|
29
|
+
const activeScopes = new Set(config.scopes ?? []);
|
|
30
|
+
// Scope vocabulary: every `@Scope` declared in the source plus the active
|
|
31
|
+
// scopes. Only these names are treated as `<scope>…</scope>` description
|
|
32
|
+
// fragments — ordinary angle-bracket prose passes through untouched.
|
|
33
|
+
const knownScopes = new Set([...index.getDeclaredScopes(), ...activeScopes]);
|
|
34
|
+
const schemaBuilder = new schema_builder_1.SchemaBuilder(index, { activeScopes, knownScopes });
|
|
35
|
+
for (const klass of config.additionalModels ?? []) {
|
|
36
|
+
const name = klass.name;
|
|
37
|
+
const astClass = index.getClass(name);
|
|
38
|
+
if (!astClass) {
|
|
39
|
+
throw new Error(`additionalModels: class "${name}" was not found in the project source tree. ` +
|
|
40
|
+
`Make sure it is defined in a .ts file under the configured rootDir.`);
|
|
41
|
+
}
|
|
42
|
+
const classScopes = (0, tags_1.getScopes)((0, tags_1.getTags)(astClass));
|
|
43
|
+
if (!(0, tags_1.isVisible)(classScopes, activeScopes)) {
|
|
44
|
+
throw new Error(`additionalModels: class "${name}" has @Scope ${formatScopes(classScopes)} ` +
|
|
45
|
+
`which doesn't match the active scopes ${formatScopes(activeScopes)}. ` +
|
|
46
|
+
`Remove it from additionalModels or add a matching scope.`);
|
|
47
|
+
}
|
|
48
|
+
schemaBuilder.registerRef(name);
|
|
49
|
+
}
|
|
50
|
+
const registeredSchemes = Object.keys(config.openapi.securitySchemes ?? {});
|
|
51
|
+
const pathBuilder = new path_builder_1.PathBuilder(index, schemaBuilder, {
|
|
52
|
+
globalPrefix: config.project?.globalPrefix,
|
|
53
|
+
hooks: config.hooks,
|
|
54
|
+
registeredSchemes,
|
|
55
|
+
activeScopes,
|
|
56
|
+
knownScopes,
|
|
57
|
+
});
|
|
58
|
+
const paths = pathBuilder.build();
|
|
59
|
+
const tags = pathBuilder.getTags();
|
|
60
|
+
// Flush the schema worklist — paths may have registered extra refs.
|
|
61
|
+
schemaBuilder.build();
|
|
62
|
+
const document = {
|
|
63
|
+
openapi: '3.0.3',
|
|
64
|
+
info: {
|
|
65
|
+
title: config.openapi.title,
|
|
66
|
+
version: config.openapi.version,
|
|
67
|
+
...(config.openapi.description ? { description: config.openapi.description } : {}),
|
|
68
|
+
...config.openapi.info,
|
|
69
|
+
},
|
|
70
|
+
paths,
|
|
71
|
+
components: {
|
|
72
|
+
schemas: schemaBuilder.getSchemas(),
|
|
73
|
+
...(config.openapi.securitySchemes
|
|
74
|
+
? { securitySchemes: config.openapi.securitySchemes }
|
|
75
|
+
: {}),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
if (config.openapi.servers && config.openapi.servers.length > 0) {
|
|
79
|
+
document.servers = config.openapi.servers;
|
|
80
|
+
}
|
|
81
|
+
if (tags.length > 0) {
|
|
82
|
+
document.tags = tags;
|
|
83
|
+
}
|
|
84
|
+
const { valid, errors } = await (0, validate_1.validateDocument)(document);
|
|
85
|
+
if (!valid) {
|
|
86
|
+
throw new Error(`Generated OpenAPI document failed schema validation:\n${errors
|
|
87
|
+
.map((e) => ` - ${e}`)
|
|
88
|
+
.join('\n')}`);
|
|
89
|
+
}
|
|
90
|
+
return document;
|
|
91
|
+
}
|
|
92
|
+
function formatScopes(scopes) {
|
|
93
|
+
return scopes.size === 0 ? '{}' : `{${[...scopes].join(', ')}}`;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { NestParserHooks } from '../config/types';
|
|
2
|
+
import { AstIndex } from './ast-index';
|
|
3
|
+
import { SchemaBuilder } from './schema-builder';
|
|
4
|
+
export interface PathBuilderOptions {
|
|
5
|
+
globalPrefix?: string;
|
|
6
|
+
hooks?: NestParserHooks;
|
|
7
|
+
registeredSchemes?: string[];
|
|
8
|
+
activeScopes?: Set<string>;
|
|
9
|
+
/** Scope vocabulary used to recognize `<scope>…</scope>` description fragments. */
|
|
10
|
+
knownScopes?: Set<string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Converts NestJS controllers into the OpenAPI `paths` object by static analysis:
|
|
14
|
+
* `@Controller` + HTTP route decorators, `@Param/@Query/@Body/@Headers`, method
|
|
15
|
+
* return types, and pluggable hooks for response envelope and security.
|
|
16
|
+
*/
|
|
17
|
+
export declare class PathBuilder {
|
|
18
|
+
private readonly index;
|
|
19
|
+
private readonly schemaBuilder;
|
|
20
|
+
private readonly paths;
|
|
21
|
+
private readonly usedOperationIds;
|
|
22
|
+
private readonly tags;
|
|
23
|
+
/** Distinct method-level `@Tag` names seen, declared in root after controllers. */
|
|
24
|
+
private readonly methodTagNames;
|
|
25
|
+
private readonly globalPrefix;
|
|
26
|
+
private readonly hooks;
|
|
27
|
+
private readonly registeredSchemes;
|
|
28
|
+
private readonly activeScopes;
|
|
29
|
+
private readonly knownScopes;
|
|
30
|
+
constructor(index: AstIndex, schemaBuilder: SchemaBuilder, options?: PathBuilderOptions);
|
|
31
|
+
build(): Record<string, Record<string, unknown>>;
|
|
32
|
+
/**
|
|
33
|
+
* Root `tags[]` entries: one per distinct tag name in play. Controller tags
|
|
34
|
+
* (name from `@Tag`/the default-tag hook, description from the class JSDoc;
|
|
35
|
+
* first controller wins on a shared name) come first, then any method-level
|
|
36
|
+
* `@Tag` name a controller didn't already declare — description-less, since a
|
|
37
|
+
* method's JSDoc is its operation description, not a tag description.
|
|
38
|
+
*/
|
|
39
|
+
getTags(): {
|
|
40
|
+
name: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
}[];
|
|
43
|
+
private processController;
|
|
44
|
+
private buildOperation;
|
|
45
|
+
/**
|
|
46
|
+
* The operation `summary`: a method-level `@Name` JSDoc tag (highest priority),
|
|
47
|
+
* else the `endpointSummary` hook when it returns a value, else the default —
|
|
48
|
+
* the method name humanized.
|
|
49
|
+
*/
|
|
50
|
+
private resolveSummary;
|
|
51
|
+
private buildQueryParameters;
|
|
52
|
+
private buildRequestBody;
|
|
53
|
+
private buildResponses;
|
|
54
|
+
/**
|
|
55
|
+
* The success status code for an operation: an explicit `@HttpCode(...)` when
|
|
56
|
+
* present — either a numeric literal (`@HttpCode(204)`) or an `HttpStatus`
|
|
57
|
+
* member (`@HttpCode(HttpStatus.NO_CONTENT)`) — otherwise NestJS's default:
|
|
58
|
+
* 201 for POST, 200 for every other verb.
|
|
59
|
+
*/
|
|
60
|
+
private responseStatus;
|
|
61
|
+
private computeResponseSchema;
|
|
62
|
+
private buildSecurity;
|
|
63
|
+
private paramSchema;
|
|
64
|
+
private stringArg;
|
|
65
|
+
/**
|
|
66
|
+
* Read a decorator argument that may be a string literal or an array of string
|
|
67
|
+
* literals (`@Get('a')` or `@Get(['a', 'b'])`), returning the distinct values.
|
|
68
|
+
* Non-string and dynamic entries are skipped; the result is empty when absent.
|
|
69
|
+
*/
|
|
70
|
+
private stringArgs;
|
|
71
|
+
private defaultTag;
|
|
72
|
+
private uniqueOperationId;
|
|
73
|
+
private joinPath;
|
|
74
|
+
/**
|
|
75
|
+
* Turn a raw NestJS route (with `:params`) into the OpenAPI path(s) it maps to.
|
|
76
|
+
*
|
|
77
|
+
* - An optional param (`:id?`) expands into the with/without pair, because
|
|
78
|
+
* OpenAPI path params are always required (`a/:id?` → `/a` and `/a/{id}`).
|
|
79
|
+
* - Constructs OpenAPI can't represent — inline regex (`:id(\d+)`), wildcards
|
|
80
|
+
* (`*`, `:splat*`), `+`/`*` modifiers, and more than one optional param —
|
|
81
|
+
* cause the route to be skipped with a warning.
|
|
82
|
+
*/
|
|
83
|
+
private expandRoutePaths;
|
|
84
|
+
private toOpenApiPath;
|
|
85
|
+
/** Names of the `{param}` placeholders in an OpenAPI path, in order, deduped. */
|
|
86
|
+
private pathParamNames;
|
|
87
|
+
}
|