@nestia/sdk 11.0.0-dev.20260316 → 11.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/LICENSE +21 -21
- package/README.md +93 -93
- package/assets/bundle/api/HttpError.ts +1 -1
- package/assets/bundle/api/IConnection.ts +1 -1
- package/assets/bundle/api/Primitive.ts +1 -1
- package/assets/bundle/api/Resolved.ts +1 -1
- package/assets/bundle/api/index.ts +4 -4
- package/assets/bundle/api/module.ts +6 -6
- package/assets/bundle/distribute/README.md +37 -37
- package/assets/bundle/distribute/package.json +28 -28
- package/assets/bundle/distribute/tsconfig.json +109 -109
- package/assets/bundle/e2e/index.ts +42 -42
- package/assets/config/nestia.config.ts +97 -97
- package/lib/executable/internal/NestiaConfigLoader.js +5 -5
- package/lib/executable/internal/NestiaConfigLoader.js.map +1 -1
- package/package.json +8 -8
- package/src/INestiaConfig.ts +267 -267
- package/src/NestiaSdkApplication.ts +307 -307
- package/src/NestiaSwaggerComposer.ts +143 -143
- package/src/analyses/AccessorAnalyzer.ts +67 -67
- package/src/analyses/DtoAnalyzer.ts +260 -260
- package/src/analyses/ImportAnalyzer.ts +126 -126
- package/src/analyses/ReflectHttpOperationAnalyzer.ts +183 -183
- package/src/analyses/ReflectHttpOperationExceptionAnalyzer.ts +72 -72
- package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +350 -350
- package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +126 -126
- package/src/analyses/TypedHttpRouteAnalyzer.ts +208 -208
- package/src/executable/internal/NestiaConfigLoader.ts +85 -85
- package/src/executable/internal/NestiaSdkCommand.ts +107 -107
- package/src/generates/SwaggerGenerator.ts +291 -291
- package/src/generates/internal/E2eFileProgrammer.ts +196 -196
- package/src/generates/internal/FilePrinter.ts +64 -64
- package/src/generates/internal/ImportDictionary.ts +192 -192
- package/src/generates/internal/SdkAliasCollection.ts +260 -260
- package/src/generates/internal/SdkFileProgrammer.ts +110 -110
- package/src/generates/internal/SdkHttpCloneProgrammer.ts +126 -126
- package/src/generates/internal/SdkHttpCloneReferencer.ts +77 -77
- package/src/generates/internal/SdkHttpFunctionProgrammer.ts +278 -278
- package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +502 -502
- package/src/generates/internal/SdkHttpRouteProgrammer.ts +109 -109
- package/src/generates/internal/SdkHttpSimulationProgrammer.ts +312 -312
- package/src/generates/internal/SdkImportWizard.ts +62 -62
- package/src/generates/internal/SdkTypeProgrammer.ts +388 -388
- package/src/generates/internal/SdkTypeTagProgrammer.ts +114 -114
- package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +379 -379
- package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +302 -302
- package/src/generates/internal/SwaggerOperationComposer.ts +119 -119
- package/src/generates/internal/SwaggerOperationParameterComposer.ts +161 -161
- package/src/generates/internal/SwaggerOperationResponseComposer.ts +110 -110
- package/src/module.ts +4 -4
- package/src/structures/IReflectHttpOperationException.ts +18 -18
- package/src/structures/IReflectHttpOperationParameter.ts +79 -79
- package/src/structures/IReflectHttpOperationSuccess.ts +21 -21
- package/src/structures/ITypedApplication.ts +11 -11
- package/src/structures/ITypedHttpRouteException.ts +15 -15
- package/src/structures/ITypedHttpRouteParameter.ts +41 -41
- package/src/structures/ITypedHttpRouteSuccess.ts +22 -22
- package/src/transformers/IOperationMetadata.ts +46 -46
- package/src/transformers/ISdkOperationTransformerContext.ts +8 -8
- package/src/transformers/SdkOperationProgrammer.ts +240 -240
- package/src/transformers/SdkOperationTransformer.ts +248 -248
- package/src/transformers/TextPlainValidator.ts +17 -17
- package/src/utils/MetadataUtil.ts +26 -26
- package/src/validators/HttpHeadersValidator.ts +40 -40
- package/src/validators/HttpQueryValidator.ts +40 -40
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import ts from "typescript";
|
|
3
|
-
|
|
4
|
-
import { IReflectImport } from "../structures/IReflectImport";
|
|
5
|
-
import { MapUtil } from "../utils/MapUtil";
|
|
6
|
-
|
|
7
|
-
export namespace ImportAnalyzer {
|
|
8
|
-
export const analyze = (file: ts.SourceFile): IReflectImport[] =>
|
|
9
|
-
file.statements
|
|
10
|
-
.filter(ts.isImportDeclaration)
|
|
11
|
-
.map((imp) => {
|
|
12
|
-
const clause: ts.ImportClause | undefined = imp.importClause;
|
|
13
|
-
if (clause === undefined) return null;
|
|
14
|
-
try {
|
|
15
|
-
// no real position
|
|
16
|
-
// import statement generated by transformer like typia
|
|
17
|
-
imp.moduleSpecifier.getText();
|
|
18
|
-
} catch {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const reflect: IReflectImport = {
|
|
23
|
-
file: normalizePath(
|
|
24
|
-
path.dirname(file.fileName),
|
|
25
|
-
ts.isStringLiteral(imp.moduleSpecifier)
|
|
26
|
-
? imp.moduleSpecifier.text
|
|
27
|
-
: escapeQuotes(imp.moduleSpecifier.getText()),
|
|
28
|
-
),
|
|
29
|
-
elements: [],
|
|
30
|
-
default: null,
|
|
31
|
-
asterisk: null,
|
|
32
|
-
};
|
|
33
|
-
if (clause.name) reflect.default = clause.name.getText();
|
|
34
|
-
if (clause.namedBindings === undefined) return reflect;
|
|
35
|
-
else if (ts.isNamedImports(clause.namedBindings))
|
|
36
|
-
reflect.elements = clause.namedBindings.elements.map((e) =>
|
|
37
|
-
e.name.getText(),
|
|
38
|
-
);
|
|
39
|
-
else if (ts.isNamespaceImport(clause.namedBindings))
|
|
40
|
-
reflect.asterisk = clause.namedBindings.name.getText();
|
|
41
|
-
return reflect;
|
|
42
|
-
})
|
|
43
|
-
.filter((r) => r !== null);
|
|
44
|
-
|
|
45
|
-
export const merge = (imports: IReflectImport[]): IReflectImport[] => {
|
|
46
|
-
// group by files
|
|
47
|
-
const fileGroups: Map<string, IReflectImport[]> = new Map();
|
|
48
|
-
for (const imp of imports) {
|
|
49
|
-
const array: IReflectImport[] = MapUtil.take(
|
|
50
|
-
fileGroups,
|
|
51
|
-
imp.file,
|
|
52
|
-
() => [],
|
|
53
|
-
);
|
|
54
|
-
array.push(imp);
|
|
55
|
-
}
|
|
56
|
-
return Array.from(fileGroups.entries())
|
|
57
|
-
.map(([key, value]) => mergeGroup(key, value))
|
|
58
|
-
.flat();
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
function mergeGroup(
|
|
62
|
-
file: string,
|
|
63
|
-
imports: IReflectImport[],
|
|
64
|
-
): IReflectImport[] {
|
|
65
|
-
const allStarImports: IReflectImport[] = Array.from(
|
|
66
|
-
new Set(
|
|
67
|
-
imports
|
|
68
|
-
.filter((imp) => imp.asterisk !== null)
|
|
69
|
-
.map((imp) => imp.asterisk!),
|
|
70
|
-
),
|
|
71
|
-
)
|
|
72
|
-
.sort()
|
|
73
|
-
.map(
|
|
74
|
-
(allStarImport) =>
|
|
75
|
-
({
|
|
76
|
-
file,
|
|
77
|
-
elements: [],
|
|
78
|
-
default: null,
|
|
79
|
-
asterisk: allStarImport,
|
|
80
|
-
}) satisfies IReflectImport,
|
|
81
|
-
);
|
|
82
|
-
const defaultImports: IReflectImport[] = Array.from(
|
|
83
|
-
new Set(
|
|
84
|
-
imports
|
|
85
|
-
.filter((imp) => imp.default !== null)
|
|
86
|
-
.map((imp) => imp.default!),
|
|
87
|
-
),
|
|
88
|
-
)
|
|
89
|
-
.sort()
|
|
90
|
-
.map(
|
|
91
|
-
(defaultImport) =>
|
|
92
|
-
({
|
|
93
|
-
file,
|
|
94
|
-
elements: [],
|
|
95
|
-
default: defaultImport,
|
|
96
|
-
asterisk: null,
|
|
97
|
-
}) satisfies IReflectImport,
|
|
98
|
-
);
|
|
99
|
-
const instances: Set<string> = new Set(
|
|
100
|
-
imports.map((imp) => imp.elements).flat(),
|
|
101
|
-
);
|
|
102
|
-
if (instances.size !== 0) {
|
|
103
|
-
if (defaultImports.length !== 0)
|
|
104
|
-
defaultImports[0]!.elements = Array.from(instances).sort();
|
|
105
|
-
else
|
|
106
|
-
defaultImports.push({
|
|
107
|
-
file,
|
|
108
|
-
elements: Array.from(instances).sort(),
|
|
109
|
-
default: null,
|
|
110
|
-
asterisk: null,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
return [...allStarImports, ...defaultImports];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function escapeQuotes(str: string): string {
|
|
117
|
-
if (str.startsWith("'") && str.endsWith("'")) return str.slice(1, -1);
|
|
118
|
-
else if (str.startsWith('"') && str.endsWith('"')) return str.slice(1, -1);
|
|
119
|
-
return str;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function normalizePath(root: string, str: string): string {
|
|
123
|
-
if (str.startsWith(".")) return path.resolve(root, str);
|
|
124
|
-
return `node_modules/${str}`;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
1
|
+
import path from "path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
import { IReflectImport } from "../structures/IReflectImport";
|
|
5
|
+
import { MapUtil } from "../utils/MapUtil";
|
|
6
|
+
|
|
7
|
+
export namespace ImportAnalyzer {
|
|
8
|
+
export const analyze = (file: ts.SourceFile): IReflectImport[] =>
|
|
9
|
+
file.statements
|
|
10
|
+
.filter(ts.isImportDeclaration)
|
|
11
|
+
.map((imp) => {
|
|
12
|
+
const clause: ts.ImportClause | undefined = imp.importClause;
|
|
13
|
+
if (clause === undefined) return null;
|
|
14
|
+
try {
|
|
15
|
+
// no real position
|
|
16
|
+
// import statement generated by transformer like typia
|
|
17
|
+
imp.moduleSpecifier.getText();
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const reflect: IReflectImport = {
|
|
23
|
+
file: normalizePath(
|
|
24
|
+
path.dirname(file.fileName),
|
|
25
|
+
ts.isStringLiteral(imp.moduleSpecifier)
|
|
26
|
+
? imp.moduleSpecifier.text
|
|
27
|
+
: escapeQuotes(imp.moduleSpecifier.getText()),
|
|
28
|
+
),
|
|
29
|
+
elements: [],
|
|
30
|
+
default: null,
|
|
31
|
+
asterisk: null,
|
|
32
|
+
};
|
|
33
|
+
if (clause.name) reflect.default = clause.name.getText();
|
|
34
|
+
if (clause.namedBindings === undefined) return reflect;
|
|
35
|
+
else if (ts.isNamedImports(clause.namedBindings))
|
|
36
|
+
reflect.elements = clause.namedBindings.elements.map((e) =>
|
|
37
|
+
e.name.getText(),
|
|
38
|
+
);
|
|
39
|
+
else if (ts.isNamespaceImport(clause.namedBindings))
|
|
40
|
+
reflect.asterisk = clause.namedBindings.name.getText();
|
|
41
|
+
return reflect;
|
|
42
|
+
})
|
|
43
|
+
.filter((r) => r !== null);
|
|
44
|
+
|
|
45
|
+
export const merge = (imports: IReflectImport[]): IReflectImport[] => {
|
|
46
|
+
// group by files
|
|
47
|
+
const fileGroups: Map<string, IReflectImport[]> = new Map();
|
|
48
|
+
for (const imp of imports) {
|
|
49
|
+
const array: IReflectImport[] = MapUtil.take(
|
|
50
|
+
fileGroups,
|
|
51
|
+
imp.file,
|
|
52
|
+
() => [],
|
|
53
|
+
);
|
|
54
|
+
array.push(imp);
|
|
55
|
+
}
|
|
56
|
+
return Array.from(fileGroups.entries())
|
|
57
|
+
.map(([key, value]) => mergeGroup(key, value))
|
|
58
|
+
.flat();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function mergeGroup(
|
|
62
|
+
file: string,
|
|
63
|
+
imports: IReflectImport[],
|
|
64
|
+
): IReflectImport[] {
|
|
65
|
+
const allStarImports: IReflectImport[] = Array.from(
|
|
66
|
+
new Set(
|
|
67
|
+
imports
|
|
68
|
+
.filter((imp) => imp.asterisk !== null)
|
|
69
|
+
.map((imp) => imp.asterisk!),
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
.sort()
|
|
73
|
+
.map(
|
|
74
|
+
(allStarImport) =>
|
|
75
|
+
({
|
|
76
|
+
file,
|
|
77
|
+
elements: [],
|
|
78
|
+
default: null,
|
|
79
|
+
asterisk: allStarImport,
|
|
80
|
+
}) satisfies IReflectImport,
|
|
81
|
+
);
|
|
82
|
+
const defaultImports: IReflectImport[] = Array.from(
|
|
83
|
+
new Set(
|
|
84
|
+
imports
|
|
85
|
+
.filter((imp) => imp.default !== null)
|
|
86
|
+
.map((imp) => imp.default!),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
.sort()
|
|
90
|
+
.map(
|
|
91
|
+
(defaultImport) =>
|
|
92
|
+
({
|
|
93
|
+
file,
|
|
94
|
+
elements: [],
|
|
95
|
+
default: defaultImport,
|
|
96
|
+
asterisk: null,
|
|
97
|
+
}) satisfies IReflectImport,
|
|
98
|
+
);
|
|
99
|
+
const instances: Set<string> = new Set(
|
|
100
|
+
imports.map((imp) => imp.elements).flat(),
|
|
101
|
+
);
|
|
102
|
+
if (instances.size !== 0) {
|
|
103
|
+
if (defaultImports.length !== 0)
|
|
104
|
+
defaultImports[0]!.elements = Array.from(instances).sort();
|
|
105
|
+
else
|
|
106
|
+
defaultImports.push({
|
|
107
|
+
file,
|
|
108
|
+
elements: Array.from(instances).sort(),
|
|
109
|
+
default: null,
|
|
110
|
+
asterisk: null,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return [...allStarImports, ...defaultImports];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function escapeQuotes(str: string): string {
|
|
117
|
+
if (str.startsWith("'") && str.endsWith("'")) return str.slice(1, -1);
|
|
118
|
+
else if (str.startsWith('"') && str.endsWith('"')) return str.slice(1, -1);
|
|
119
|
+
return str;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizePath(root: string, str: string): string {
|
|
123
|
+
if (str.startsWith(".")) return path.resolve(root, str);
|
|
124
|
+
return `node_modules/${str}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
import { METHOD_METADATA, PATH_METADATA } from "@nestjs/common/constants";
|
|
2
|
-
import { ranges } from "tstl";
|
|
3
|
-
|
|
4
|
-
import { INestiaProject } from "../structures/INestiaProject";
|
|
5
|
-
import { IReflectController } from "../structures/IReflectController";
|
|
6
|
-
import { IReflectHttpOperation } from "../structures/IReflectHttpOperation";
|
|
7
|
-
import { IReflectHttpOperationParameter } from "../structures/IReflectHttpOperationParameter";
|
|
8
|
-
import { IReflectHttpOperationSuccess } from "../structures/IReflectHttpOperationSuccess";
|
|
9
|
-
import { IReflectOperationError } from "../structures/IReflectOperationError";
|
|
10
|
-
import { IOperationMetadata } from "../transformers/IOperationMetadata";
|
|
11
|
-
import { ArrayUtil } from "../utils/ArrayUtil";
|
|
12
|
-
import { ImportAnalyzer } from "./ImportAnalyzer";
|
|
13
|
-
import { PathAnalyzer } from "./PathAnalyzer";
|
|
14
|
-
import { ReflectHttpOperationExceptionAnalyzer } from "./ReflectHttpOperationExceptionAnalyzer";
|
|
15
|
-
import { ReflectHttpOperationParameterAnalyzer } from "./ReflectHttpOperationParameterAnalyzer";
|
|
16
|
-
import { ReflectHttpOperationResponseAnalyzer } from "./ReflectHttpOperationResponseAnalyzer";
|
|
17
|
-
import { ReflectMetadataAnalyzer } from "./ReflectMetadataAnalyzer";
|
|
18
|
-
|
|
19
|
-
export namespace ReflectHttpOperationAnalyzer {
|
|
20
|
-
export interface IProps {
|
|
21
|
-
project: Omit<INestiaProject, "config">;
|
|
22
|
-
controller: IReflectController;
|
|
23
|
-
function: Function;
|
|
24
|
-
name: string;
|
|
25
|
-
metadata: IOperationMetadata;
|
|
26
|
-
}
|
|
27
|
-
export const analyze = (props: IProps): IReflectHttpOperation | null => {
|
|
28
|
-
if (
|
|
29
|
-
ArrayUtil.has(
|
|
30
|
-
Reflect.getMetadataKeys(props.function),
|
|
31
|
-
PATH_METADATA,
|
|
32
|
-
METHOD_METADATA,
|
|
33
|
-
) === false
|
|
34
|
-
)
|
|
35
|
-
return null;
|
|
36
|
-
|
|
37
|
-
const errors: IReflectOperationError[] = [];
|
|
38
|
-
const method: string =
|
|
39
|
-
METHODS[Reflect.getMetadata(METHOD_METADATA, props.function)]!;
|
|
40
|
-
if (method === undefined || method === "OPTIONS") return null;
|
|
41
|
-
|
|
42
|
-
const parameters: IReflectHttpOperationParameter[] =
|
|
43
|
-
ReflectHttpOperationParameterAnalyzer.analyze({
|
|
44
|
-
controller: props.controller,
|
|
45
|
-
metadata: props.metadata,
|
|
46
|
-
httpMethod: method,
|
|
47
|
-
function: props.function,
|
|
48
|
-
functionName: props.name,
|
|
49
|
-
errors,
|
|
50
|
-
});
|
|
51
|
-
const success: IReflectHttpOperationSuccess | null = (() => {
|
|
52
|
-
const localErrors: IReflectOperationError[] = [];
|
|
53
|
-
const success = ReflectHttpOperationResponseAnalyzer.analyze({
|
|
54
|
-
controller: props.controller,
|
|
55
|
-
function: props.function,
|
|
56
|
-
functionName: props.name,
|
|
57
|
-
httpMethod: method,
|
|
58
|
-
metadata: props.metadata,
|
|
59
|
-
errors,
|
|
60
|
-
});
|
|
61
|
-
if (localErrors.length) {
|
|
62
|
-
errors.push(...localErrors);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
return success;
|
|
66
|
-
})();
|
|
67
|
-
if (errors.length) {
|
|
68
|
-
props.project.errors.push(...errors);
|
|
69
|
-
return null;
|
|
70
|
-
} else if (success === null) return null;
|
|
71
|
-
|
|
72
|
-
// DO CONSTRUCT
|
|
73
|
-
const operation: IReflectHttpOperation = {
|
|
74
|
-
protocol: "http",
|
|
75
|
-
function: props.function,
|
|
76
|
-
name: props.name,
|
|
77
|
-
method: method === "ALL" ? "POST" : method,
|
|
78
|
-
paths: ReflectMetadataAnalyzer.paths(props.function).filter((str) => {
|
|
79
|
-
if (str.includes("*") === true) {
|
|
80
|
-
props.project.warnings.push({
|
|
81
|
-
file: props.controller.file,
|
|
82
|
-
class: props.controller.class.name,
|
|
83
|
-
function: props.name,
|
|
84
|
-
from: "",
|
|
85
|
-
contents: ["@nestia/sdk does not compose wildcard method."],
|
|
86
|
-
});
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
return true;
|
|
90
|
-
}),
|
|
91
|
-
versions: ReflectMetadataAnalyzer.versions(props.function),
|
|
92
|
-
parameters,
|
|
93
|
-
success,
|
|
94
|
-
security: ReflectMetadataAnalyzer.securities(props.function),
|
|
95
|
-
exceptions: ReflectHttpOperationExceptionAnalyzer.analyze({
|
|
96
|
-
controller: props.controller,
|
|
97
|
-
function: props.function,
|
|
98
|
-
functionName: props.name,
|
|
99
|
-
httpMethod: method,
|
|
100
|
-
metadata: props.metadata,
|
|
101
|
-
errors,
|
|
102
|
-
}),
|
|
103
|
-
tags: Reflect.getMetadata("swagger/apiUseTags", props.function) ?? [],
|
|
104
|
-
imports: ImportAnalyzer.merge(
|
|
105
|
-
[
|
|
106
|
-
...props.metadata.parameters
|
|
107
|
-
.filter((x) => parameters.some((y) => x.index === y.index))
|
|
108
|
-
.map((x) => x.imports),
|
|
109
|
-
...props.metadata.success.imports,
|
|
110
|
-
...Object.values(props.metadata.exceptions).map((e) => e.imports),
|
|
111
|
-
].flat(),
|
|
112
|
-
),
|
|
113
|
-
description: props.metadata.description,
|
|
114
|
-
jsDocTags: props.metadata.jsDocTags,
|
|
115
|
-
operationId: props.metadata.jsDocTags
|
|
116
|
-
.find(({ name }) => name === "operationId")
|
|
117
|
-
?.text?.[0]?.text.split(" ")[0]
|
|
118
|
-
?.trim(),
|
|
119
|
-
extensions: ReflectMetadataAnalyzer.extensions(props.function),
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// VALIDATE PATH ARGUMENTS
|
|
123
|
-
for (const controllerLocation of props.controller.paths)
|
|
124
|
-
for (const metaLocation of operation.paths) {
|
|
125
|
-
// NORMALIZE LOCATION
|
|
126
|
-
const location: string = PathAnalyzer.join(
|
|
127
|
-
controllerLocation,
|
|
128
|
-
metaLocation,
|
|
129
|
-
);
|
|
130
|
-
if (location.includes("*")) continue;
|
|
131
|
-
|
|
132
|
-
// LIST UP PARAMETERS
|
|
133
|
-
const binded: string[] | null = PathAnalyzer.parameters(location);
|
|
134
|
-
if (binded === null) {
|
|
135
|
-
props.project.errors.push({
|
|
136
|
-
file: props.controller.file,
|
|
137
|
-
class: props.controller.class.name,
|
|
138
|
-
function: props.name,
|
|
139
|
-
from: "{parameters}",
|
|
140
|
-
contents: [`invalid path (${JSON.stringify(location)})`],
|
|
141
|
-
});
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
const parameters: string[] = operation.parameters
|
|
145
|
-
.filter((param) => param.category === "param")
|
|
146
|
-
.map((param) => param.field!)
|
|
147
|
-
.sort();
|
|
148
|
-
|
|
149
|
-
// DO VALIDATE
|
|
150
|
-
if (ranges.equal(binded.sort(), parameters) === false)
|
|
151
|
-
errors.push({
|
|
152
|
-
file: props.controller.file,
|
|
153
|
-
class: props.controller.class.name,
|
|
154
|
-
function: props.name,
|
|
155
|
-
from: "{parameters}",
|
|
156
|
-
contents: [
|
|
157
|
-
`binded arguments in the "path" between function's decorator and parameters' decorators are different (function: [${binded.join(
|
|
158
|
-
", ",
|
|
159
|
-
)}], parameters: [${parameters.join(", ")}]).`,
|
|
160
|
-
],
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// RETURNS
|
|
165
|
-
if (errors.length) {
|
|
166
|
-
props.project.errors.push(...errors);
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
return operation;
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// node_modules/@nestjs/common/lib/enums/request-method.enum.ts
|
|
174
|
-
const METHODS = [
|
|
175
|
-
"GET",
|
|
176
|
-
"POST",
|
|
177
|
-
"PUT",
|
|
178
|
-
"DELETE",
|
|
179
|
-
"PATCH",
|
|
180
|
-
"ALL",
|
|
181
|
-
"OPTIONS",
|
|
182
|
-
"HEAD",
|
|
183
|
-
];
|
|
1
|
+
import { METHOD_METADATA, PATH_METADATA } from "@nestjs/common/constants";
|
|
2
|
+
import { ranges } from "tstl";
|
|
3
|
+
|
|
4
|
+
import { INestiaProject } from "../structures/INestiaProject";
|
|
5
|
+
import { IReflectController } from "../structures/IReflectController";
|
|
6
|
+
import { IReflectHttpOperation } from "../structures/IReflectHttpOperation";
|
|
7
|
+
import { IReflectHttpOperationParameter } from "../structures/IReflectHttpOperationParameter";
|
|
8
|
+
import { IReflectHttpOperationSuccess } from "../structures/IReflectHttpOperationSuccess";
|
|
9
|
+
import { IReflectOperationError } from "../structures/IReflectOperationError";
|
|
10
|
+
import { IOperationMetadata } from "../transformers/IOperationMetadata";
|
|
11
|
+
import { ArrayUtil } from "../utils/ArrayUtil";
|
|
12
|
+
import { ImportAnalyzer } from "./ImportAnalyzer";
|
|
13
|
+
import { PathAnalyzer } from "./PathAnalyzer";
|
|
14
|
+
import { ReflectHttpOperationExceptionAnalyzer } from "./ReflectHttpOperationExceptionAnalyzer";
|
|
15
|
+
import { ReflectHttpOperationParameterAnalyzer } from "./ReflectHttpOperationParameterAnalyzer";
|
|
16
|
+
import { ReflectHttpOperationResponseAnalyzer } from "./ReflectHttpOperationResponseAnalyzer";
|
|
17
|
+
import { ReflectMetadataAnalyzer } from "./ReflectMetadataAnalyzer";
|
|
18
|
+
|
|
19
|
+
export namespace ReflectHttpOperationAnalyzer {
|
|
20
|
+
export interface IProps {
|
|
21
|
+
project: Omit<INestiaProject, "config">;
|
|
22
|
+
controller: IReflectController;
|
|
23
|
+
function: Function;
|
|
24
|
+
name: string;
|
|
25
|
+
metadata: IOperationMetadata;
|
|
26
|
+
}
|
|
27
|
+
export const analyze = (props: IProps): IReflectHttpOperation | null => {
|
|
28
|
+
if (
|
|
29
|
+
ArrayUtil.has(
|
|
30
|
+
Reflect.getMetadataKeys(props.function),
|
|
31
|
+
PATH_METADATA,
|
|
32
|
+
METHOD_METADATA,
|
|
33
|
+
) === false
|
|
34
|
+
)
|
|
35
|
+
return null;
|
|
36
|
+
|
|
37
|
+
const errors: IReflectOperationError[] = [];
|
|
38
|
+
const method: string =
|
|
39
|
+
METHODS[Reflect.getMetadata(METHOD_METADATA, props.function)]!;
|
|
40
|
+
if (method === undefined || method === "OPTIONS") return null;
|
|
41
|
+
|
|
42
|
+
const parameters: IReflectHttpOperationParameter[] =
|
|
43
|
+
ReflectHttpOperationParameterAnalyzer.analyze({
|
|
44
|
+
controller: props.controller,
|
|
45
|
+
metadata: props.metadata,
|
|
46
|
+
httpMethod: method,
|
|
47
|
+
function: props.function,
|
|
48
|
+
functionName: props.name,
|
|
49
|
+
errors,
|
|
50
|
+
});
|
|
51
|
+
const success: IReflectHttpOperationSuccess | null = (() => {
|
|
52
|
+
const localErrors: IReflectOperationError[] = [];
|
|
53
|
+
const success = ReflectHttpOperationResponseAnalyzer.analyze({
|
|
54
|
+
controller: props.controller,
|
|
55
|
+
function: props.function,
|
|
56
|
+
functionName: props.name,
|
|
57
|
+
httpMethod: method,
|
|
58
|
+
metadata: props.metadata,
|
|
59
|
+
errors,
|
|
60
|
+
});
|
|
61
|
+
if (localErrors.length) {
|
|
62
|
+
errors.push(...localErrors);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return success;
|
|
66
|
+
})();
|
|
67
|
+
if (errors.length) {
|
|
68
|
+
props.project.errors.push(...errors);
|
|
69
|
+
return null;
|
|
70
|
+
} else if (success === null) return null;
|
|
71
|
+
|
|
72
|
+
// DO CONSTRUCT
|
|
73
|
+
const operation: IReflectHttpOperation = {
|
|
74
|
+
protocol: "http",
|
|
75
|
+
function: props.function,
|
|
76
|
+
name: props.name,
|
|
77
|
+
method: method === "ALL" ? "POST" : method,
|
|
78
|
+
paths: ReflectMetadataAnalyzer.paths(props.function).filter((str) => {
|
|
79
|
+
if (str.includes("*") === true) {
|
|
80
|
+
props.project.warnings.push({
|
|
81
|
+
file: props.controller.file,
|
|
82
|
+
class: props.controller.class.name,
|
|
83
|
+
function: props.name,
|
|
84
|
+
from: "",
|
|
85
|
+
contents: ["@nestia/sdk does not compose wildcard method."],
|
|
86
|
+
});
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}),
|
|
91
|
+
versions: ReflectMetadataAnalyzer.versions(props.function),
|
|
92
|
+
parameters,
|
|
93
|
+
success,
|
|
94
|
+
security: ReflectMetadataAnalyzer.securities(props.function),
|
|
95
|
+
exceptions: ReflectHttpOperationExceptionAnalyzer.analyze({
|
|
96
|
+
controller: props.controller,
|
|
97
|
+
function: props.function,
|
|
98
|
+
functionName: props.name,
|
|
99
|
+
httpMethod: method,
|
|
100
|
+
metadata: props.metadata,
|
|
101
|
+
errors,
|
|
102
|
+
}),
|
|
103
|
+
tags: Reflect.getMetadata("swagger/apiUseTags", props.function) ?? [],
|
|
104
|
+
imports: ImportAnalyzer.merge(
|
|
105
|
+
[
|
|
106
|
+
...props.metadata.parameters
|
|
107
|
+
.filter((x) => parameters.some((y) => x.index === y.index))
|
|
108
|
+
.map((x) => x.imports),
|
|
109
|
+
...props.metadata.success.imports,
|
|
110
|
+
...Object.values(props.metadata.exceptions).map((e) => e.imports),
|
|
111
|
+
].flat(),
|
|
112
|
+
),
|
|
113
|
+
description: props.metadata.description,
|
|
114
|
+
jsDocTags: props.metadata.jsDocTags,
|
|
115
|
+
operationId: props.metadata.jsDocTags
|
|
116
|
+
.find(({ name }) => name === "operationId")
|
|
117
|
+
?.text?.[0]?.text.split(" ")[0]
|
|
118
|
+
?.trim(),
|
|
119
|
+
extensions: ReflectMetadataAnalyzer.extensions(props.function),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// VALIDATE PATH ARGUMENTS
|
|
123
|
+
for (const controllerLocation of props.controller.paths)
|
|
124
|
+
for (const metaLocation of operation.paths) {
|
|
125
|
+
// NORMALIZE LOCATION
|
|
126
|
+
const location: string = PathAnalyzer.join(
|
|
127
|
+
controllerLocation,
|
|
128
|
+
metaLocation,
|
|
129
|
+
);
|
|
130
|
+
if (location.includes("*")) continue;
|
|
131
|
+
|
|
132
|
+
// LIST UP PARAMETERS
|
|
133
|
+
const binded: string[] | null = PathAnalyzer.parameters(location);
|
|
134
|
+
if (binded === null) {
|
|
135
|
+
props.project.errors.push({
|
|
136
|
+
file: props.controller.file,
|
|
137
|
+
class: props.controller.class.name,
|
|
138
|
+
function: props.name,
|
|
139
|
+
from: "{parameters}",
|
|
140
|
+
contents: [`invalid path (${JSON.stringify(location)})`],
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const parameters: string[] = operation.parameters
|
|
145
|
+
.filter((param) => param.category === "param")
|
|
146
|
+
.map((param) => param.field!)
|
|
147
|
+
.sort();
|
|
148
|
+
|
|
149
|
+
// DO VALIDATE
|
|
150
|
+
if (ranges.equal(binded.sort(), parameters) === false)
|
|
151
|
+
errors.push({
|
|
152
|
+
file: props.controller.file,
|
|
153
|
+
class: props.controller.class.name,
|
|
154
|
+
function: props.name,
|
|
155
|
+
from: "{parameters}",
|
|
156
|
+
contents: [
|
|
157
|
+
`binded arguments in the "path" between function's decorator and parameters' decorators are different (function: [${binded.join(
|
|
158
|
+
", ",
|
|
159
|
+
)}], parameters: [${parameters.join(", ")}]).`,
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// RETURNS
|
|
165
|
+
if (errors.length) {
|
|
166
|
+
props.project.errors.push(...errors);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return operation;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// node_modules/@nestjs/common/lib/enums/request-method.enum.ts
|
|
174
|
+
const METHODS = [
|
|
175
|
+
"GET",
|
|
176
|
+
"POST",
|
|
177
|
+
"PUT",
|
|
178
|
+
"DELETE",
|
|
179
|
+
"PATCH",
|
|
180
|
+
"ALL",
|
|
181
|
+
"OPTIONS",
|
|
182
|
+
"HEAD",
|
|
183
|
+
];
|