@nestia/sdk 2.6.2 → 2.6.3
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/lib/analyses/ControllerAnalyzer.js +3 -3
- package/lib/analyses/ControllerAnalyzer.js.map +1 -1
- package/lib/analyses/ImportAnalyzer.d.ts +1 -2
- package/lib/analyses/ImportAnalyzer.js +2 -2
- package/lib/analyses/ImportAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectAnalyzer.js +2 -2
- package/lib/analyses/ReflectAnalyzer.js.map +1 -1
- package/lib/generates/SwaggerGenerator.js +4 -4
- package/lib/generates/SwaggerGenerator.js.map +1 -1
- package/lib/generates/internal/ImportDictionary.js +6 -8
- package/lib/generates/internal/ImportDictionary.js.map +1 -1
- package/lib/structures/TypeEntry.js +2 -2
- package/lib/structures/TypeEntry.js.map +1 -1
- package/package.json +4 -4
- package/src/INestiaConfig.ts +248 -248
- package/src/NestiaSdkApplication.ts +255 -255
- package/src/analyses/ControllerAnalyzer.ts +402 -402
- package/src/analyses/ExceptionAnalyzer.ts +148 -148
- package/src/analyses/ImportAnalyzer.ts +1 -2
- package/src/analyses/ReflectAnalyzer.ts +463 -463
- package/src/analyses/SecurityAnalyzer.ts +24 -24
- package/src/generates/CloneGenerator.ts +62 -62
- package/src/generates/E2eGenerator.ts +66 -66
- package/src/generates/SdkGenerator.ts +84 -84
- package/src/generates/SwaggerGenerator.ts +446 -446
- package/src/generates/internal/E2eFileProgrammer.ts +182 -182
- package/src/generates/internal/FilePrinter.ts +53 -53
- package/src/generates/internal/ImportDictionary.ts +147 -149
- package/src/generates/internal/SdkAliasCollection.ts +152 -152
- package/src/generates/internal/SdkCloneProgrammer.ts +155 -155
- package/src/generates/internal/SdkFileProgrammer.ts +115 -115
- package/src/generates/internal/SdkFunctionProgrammer.ts +298 -298
- package/src/generates/internal/SdkImportWizard.ts +55 -55
- package/src/generates/internal/SdkNamespaceProgrammer.ts +510 -510
- package/src/generates/internal/SdkRouteProgrammer.ts +83 -83
- package/src/generates/internal/SdkSimulationProgrammer.ts +365 -365
- package/src/generates/internal/SdkTypeProgrammer.ts +385 -385
- package/src/generates/internal/SwaggerSchemaGenerator.ts +438 -438
- package/src/structures/IController.ts +94 -94
- package/src/structures/IRoute.ts +53 -53
- package/src/structures/ISwagger.ts +91 -91
- package/src/structures/ISwaggerRoute.ts +54 -54
- package/src/structures/ISwaggerSecurityScheme.ts +65 -65
- package/src/structures/ParamCategory.ts +1 -1
- package/src/structures/TypeEntry.ts +1 -1
- package/src/utils/StringUtil.ts +6 -6
|
@@ -1,463 +1,463 @@
|
|
|
1
|
-
import * as Constants from "@nestjs/common/constants";
|
|
2
|
-
import { RouteParamtypes } from "@nestjs/common/enums/route-paramtypes.enum";
|
|
3
|
-
import { VERSION_NEUTRAL, VersionValue } from "@nestjs/common/interfaces";
|
|
4
|
-
import "reflect-metadata";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import { IController } from "../structures/IController";
|
|
8
|
-
import { IErrorReport } from "../structures/IErrorReport";
|
|
9
|
-
import { INestiaProject } from "../structures/INestiaProject";
|
|
10
|
-
import { ParamCategory } from "../structures/ParamCategory";
|
|
11
|
-
import { ArrayUtil } from "../utils/ArrayUtil";
|
|
12
|
-
import { PathAnalyzer } from "./PathAnalyzer";
|
|
13
|
-
import { SecurityAnalyzer } from "./SecurityAnalyzer";
|
|
14
|
-
|
|
15
|
-
type IModule = {
|
|
16
|
-
[key: string]: any;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export namespace ReflectAnalyzer {
|
|
20
|
-
export const analyze =
|
|
21
|
-
(project: INestiaProject) =>
|
|
22
|
-
async (
|
|
23
|
-
unique: WeakSet<any>,
|
|
24
|
-
file: string,
|
|
25
|
-
prefixes: string[],
|
|
26
|
-
target?: Function,
|
|
27
|
-
): Promise<IController[]> => {
|
|
28
|
-
const module: IModule = await (async () => {
|
|
29
|
-
try {
|
|
30
|
-
return await import(file);
|
|
31
|
-
} catch (exp) {
|
|
32
|
-
console.log(
|
|
33
|
-
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
|
|
34
|
-
);
|
|
35
|
-
console.log(`Error on "${file}" file. Check your code.`);
|
|
36
|
-
console.log(exp);
|
|
37
|
-
console.log(
|
|
38
|
-
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
|
|
39
|
-
);
|
|
40
|
-
process.exit(-1);
|
|
41
|
-
}
|
|
42
|
-
})();
|
|
43
|
-
const ret: IController[] = [];
|
|
44
|
-
|
|
45
|
-
for (const [key, value] of Object.entries(module)) {
|
|
46
|
-
if (typeof value !== "function" || unique.has(value)) continue;
|
|
47
|
-
else if ((target ?? value) !== value) continue;
|
|
48
|
-
else unique.add(value);
|
|
49
|
-
|
|
50
|
-
const result: IController | null = _Analyze_controller(project)(
|
|
51
|
-
file,
|
|
52
|
-
key,
|
|
53
|
-
value,
|
|
54
|
-
prefixes,
|
|
55
|
-
);
|
|
56
|
-
if (result !== null) ret.push(result);
|
|
57
|
-
}
|
|
58
|
-
return ret;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/* ---------------------------------------------------------
|
|
62
|
-
CONTROLLER
|
|
63
|
-
--------------------------------------------------------- */
|
|
64
|
-
const _Analyze_controller =
|
|
65
|
-
(project: INestiaProject) =>
|
|
66
|
-
(
|
|
67
|
-
file: string,
|
|
68
|
-
name: string,
|
|
69
|
-
creator: any,
|
|
70
|
-
prefixes: string[],
|
|
71
|
-
): IController | null => {
|
|
72
|
-
//----
|
|
73
|
-
// VALIDATIONS
|
|
74
|
-
//----
|
|
75
|
-
// MUST BE TYPE OF A CREATOR WHO HAS THE CONSTRUCTOR
|
|
76
|
-
if (
|
|
77
|
-
!(
|
|
78
|
-
creator instanceof Function && creator.constructor instanceof Function
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
return null;
|
|
82
|
-
// MUST HAVE THOSE MATADATA
|
|
83
|
-
else if (
|
|
84
|
-
ArrayUtil.has(
|
|
85
|
-
Reflect.getMetadataKeys(creator),
|
|
86
|
-
Constants.PATH_METADATA,
|
|
87
|
-
Constants.HOST_METADATA,
|
|
88
|
-
Constants.SCOPE_OPTIONS_METADATA,
|
|
89
|
-
) === false
|
|
90
|
-
)
|
|
91
|
-
return null;
|
|
92
|
-
|
|
93
|
-
//----
|
|
94
|
-
// CONSTRUCTION
|
|
95
|
-
//----
|
|
96
|
-
// BASIC INFO
|
|
97
|
-
const meta: IController = {
|
|
98
|
-
target: creator,
|
|
99
|
-
file,
|
|
100
|
-
name,
|
|
101
|
-
functions: [],
|
|
102
|
-
prefixes,
|
|
103
|
-
paths: _Get_paths(creator).filter((str) => {
|
|
104
|
-
if (str.includes("*") === true) {
|
|
105
|
-
project.warnings.push({
|
|
106
|
-
file,
|
|
107
|
-
controller: name,
|
|
108
|
-
function: null,
|
|
109
|
-
message: "@nestia/sdk does not compose wildcard controller.",
|
|
110
|
-
});
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
return true;
|
|
114
|
-
}),
|
|
115
|
-
versions: _Get_versions(creator),
|
|
116
|
-
security: _Get_securities(creator),
|
|
117
|
-
swaggerTgas: Reflect.getMetadata("swagger/apiUseTags", creator) ?? [],
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// PARSE CHILDREN DATA
|
|
121
|
-
for (const tuple of _Get_prototype_entries(creator)) {
|
|
122
|
-
const child: IController.IFunction | null = _Analyze_function(project)(
|
|
123
|
-
creator.prototype,
|
|
124
|
-
meta,
|
|
125
|
-
...tuple,
|
|
126
|
-
);
|
|
127
|
-
if (child !== null) meta.functions.push(child);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// RETURNS
|
|
131
|
-
return meta;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
function _Get_prototype_entries(creator: any): Array<[string, unknown]> {
|
|
135
|
-
const keyList = Object.getOwnPropertyNames(creator.prototype);
|
|
136
|
-
const entries: Array<[string, unknown]> = keyList.map((key) => [
|
|
137
|
-
key,
|
|
138
|
-
creator.prototype[key],
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
const parent = Object.getPrototypeOf(creator);
|
|
142
|
-
if (parent.prototype !== undefined)
|
|
143
|
-
entries.push(..._Get_prototype_entries(parent));
|
|
144
|
-
|
|
145
|
-
return entries;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function _Get_paths(target: any): string[] {
|
|
149
|
-
const value: string | string[] = Reflect.getMetadata(
|
|
150
|
-
Constants.PATH_METADATA,
|
|
151
|
-
target,
|
|
152
|
-
);
|
|
153
|
-
if (typeof value === "string") return [value];
|
|
154
|
-
else if (value.length === 0) return [""];
|
|
155
|
-
else return value;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function _Get_versions(
|
|
159
|
-
target: any,
|
|
160
|
-
):
|
|
161
|
-
| Array<Exclude<VersionValue, Array<string | typeof VERSION_NEUTRAL>>>
|
|
162
|
-
| undefined {
|
|
163
|
-
const value: VersionValue | undefined = Reflect.getMetadata(
|
|
164
|
-
Constants.VERSION_METADATA,
|
|
165
|
-
target,
|
|
166
|
-
);
|
|
167
|
-
return value === undefined || Array.isArray(value) ? value : [value];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function _Get_securities(value: any): Record<string, string[]>[] {
|
|
171
|
-
const entire: Record<string, string[]>[] | undefined = Reflect.getMetadata(
|
|
172
|
-
"swagger/apiSecurity",
|
|
173
|
-
value,
|
|
174
|
-
);
|
|
175
|
-
return entire ? SecurityAnalyzer.merge(...entire) : [];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function _Get_exceptions(
|
|
179
|
-
value: any,
|
|
180
|
-
): Record<number | "2XX" | "3XX" | "4XX" | "5XX", IController.IException> {
|
|
181
|
-
const entire: IController.IException[] | undefined = Reflect.getMetadata(
|
|
182
|
-
"nestia/TypedException",
|
|
183
|
-
value,
|
|
184
|
-
);
|
|
185
|
-
return Object.fromEntries(
|
|
186
|
-
(entire ?? []).map((exp) => [exp.status, exp]),
|
|
187
|
-
) as Record<number | "2XX" | "3XX" | "4XX" | "5XX", IController.IException>;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/* ---------------------------------------------------------
|
|
191
|
-
FUNCTION
|
|
192
|
-
--------------------------------------------------------- */
|
|
193
|
-
const _Analyze_function =
|
|
194
|
-
(project: INestiaProject) =>
|
|
195
|
-
(
|
|
196
|
-
classProto: any,
|
|
197
|
-
controller: IController,
|
|
198
|
-
name: string,
|
|
199
|
-
proto: any,
|
|
200
|
-
): IController.IFunction | null => {
|
|
201
|
-
//----
|
|
202
|
-
// VALIDATIONS
|
|
203
|
-
//----
|
|
204
|
-
// MUST BE TYPE OF A FUNCTION
|
|
205
|
-
if (!(proto instanceof Function)) return null;
|
|
206
|
-
// MUST HAVE THOSE METADATE
|
|
207
|
-
else if (
|
|
208
|
-
ArrayUtil.has(
|
|
209
|
-
Reflect.getMetadataKeys(proto),
|
|
210
|
-
Constants.PATH_METADATA,
|
|
211
|
-
Constants.METHOD_METADATA,
|
|
212
|
-
) === false
|
|
213
|
-
)
|
|
214
|
-
return null;
|
|
215
|
-
|
|
216
|
-
const errors: IErrorReport[] = [];
|
|
217
|
-
|
|
218
|
-
//----
|
|
219
|
-
// CONSTRUCTION
|
|
220
|
-
//----
|
|
221
|
-
// BASIC INFO
|
|
222
|
-
const encrypted: boolean =
|
|
223
|
-
Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
|
|
224
|
-
?.constructor?.name === "EncryptedRouteInterceptor";
|
|
225
|
-
const query: boolean =
|
|
226
|
-
Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
|
|
227
|
-
?.constructor?.name === "TypedQueryRouteInterceptor";
|
|
228
|
-
const method: string =
|
|
229
|
-
METHODS[Reflect.getMetadata(Constants.METHOD_METADATA, proto)];
|
|
230
|
-
if (method === undefined || method === "OPTIONS") return null;
|
|
231
|
-
|
|
232
|
-
const parameters: IController.IParameter[] = (() => {
|
|
233
|
-
const nestParameters: NestParameters | undefined = Reflect.getMetadata(
|
|
234
|
-
Constants.ROUTE_ARGS_METADATA,
|
|
235
|
-
classProto.constructor,
|
|
236
|
-
name,
|
|
237
|
-
);
|
|
238
|
-
if (nestParameters === undefined) return [];
|
|
239
|
-
|
|
240
|
-
const output: IController.IParameter[] = [];
|
|
241
|
-
for (const tuple of Object.entries(nestParameters)) {
|
|
242
|
-
const child: IController.IParameter | null = _Analyze_parameter(
|
|
243
|
-
...tuple,
|
|
244
|
-
);
|
|
245
|
-
if (child !== null) output.push(child);
|
|
246
|
-
}
|
|
247
|
-
return output.sort((x, y) => x.index - y.index);
|
|
248
|
-
})();
|
|
249
|
-
|
|
250
|
-
// VALIDATE BODY
|
|
251
|
-
const body: IController.IParameter | undefined = parameters.find(
|
|
252
|
-
(param) => param.category === "body",
|
|
253
|
-
);
|
|
254
|
-
if (body !== undefined && (method === "GET" || method === "HEAD")) {
|
|
255
|
-
project.errors.push({
|
|
256
|
-
file: controller.file,
|
|
257
|
-
controller: controller.name,
|
|
258
|
-
function: name,
|
|
259
|
-
message: `"body" parameter cannot be used in the "${method}" method.`,
|
|
260
|
-
});
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// DO CONSTRUCT
|
|
265
|
-
const meta: IController.IFunction = {
|
|
266
|
-
target: proto,
|
|
267
|
-
name,
|
|
268
|
-
method: method === "ALL" ? "POST" : method,
|
|
269
|
-
paths: _Get_paths(proto).filter((str) => {
|
|
270
|
-
if (str.includes("*") === true) {
|
|
271
|
-
project.warnings.push({
|
|
272
|
-
file: controller.file,
|
|
273
|
-
controller: controller.name,
|
|
274
|
-
function: name,
|
|
275
|
-
message: "@nestia/sdk does not compose wildcard method.",
|
|
276
|
-
});
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
return true;
|
|
280
|
-
}),
|
|
281
|
-
versions: _Get_versions(proto),
|
|
282
|
-
parameters,
|
|
283
|
-
status: Reflect.getMetadata(Constants.HTTP_CODE_METADATA, proto),
|
|
284
|
-
encrypted,
|
|
285
|
-
contentType: encrypted
|
|
286
|
-
? "text/plain"
|
|
287
|
-
: query
|
|
288
|
-
? "application/x-www-form-urlencoded"
|
|
289
|
-
: Reflect.getMetadata(Constants.HEADERS_METADATA, proto)?.find(
|
|
290
|
-
(h: Record<string, string>) =>
|
|
291
|
-
typeof h?.name === "string" &&
|
|
292
|
-
typeof h?.value === "string" &&
|
|
293
|
-
h.name.toLowerCase() === "content-type",
|
|
294
|
-
)?.value ?? "application/json",
|
|
295
|
-
security: _Get_securities(proto),
|
|
296
|
-
exceptions: _Get_exceptions(proto),
|
|
297
|
-
swaggerTags: [
|
|
298
|
-
...new Set([
|
|
299
|
-
...controller.swaggerTgas,
|
|
300
|
-
...(Reflect.getMetadata("swagger/apiUseTags", proto) ?? []),
|
|
301
|
-
]),
|
|
302
|
-
],
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
// VALIDATE PATH ARGUMENTS
|
|
306
|
-
for (const controllerLocation of controller.paths)
|
|
307
|
-
for (const metaLocation of meta.paths) {
|
|
308
|
-
// NORMALIZE LOCATION
|
|
309
|
-
const location: string = PathAnalyzer.join(
|
|
310
|
-
controllerLocation,
|
|
311
|
-
metaLocation,
|
|
312
|
-
);
|
|
313
|
-
if (location.includes("*")) continue;
|
|
314
|
-
|
|
315
|
-
// LIST UP PARAMETERS
|
|
316
|
-
const binded: string[] | null = PathAnalyzer.parameters(location);
|
|
317
|
-
if (binded === null) {
|
|
318
|
-
project.errors.push({
|
|
319
|
-
file: controller.file,
|
|
320
|
-
controller: controller.name,
|
|
321
|
-
function: name,
|
|
322
|
-
message: `invalid path ("${location}")`,
|
|
323
|
-
});
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
const parameters: string[] = meta.parameters
|
|
327
|
-
.filter((param) => param.category === "param")
|
|
328
|
-
.map((param) => param.field!)
|
|
329
|
-
.sort();
|
|
330
|
-
|
|
331
|
-
// DO VALIDATE
|
|
332
|
-
if (equal(binded.sort(), parameters) === false)
|
|
333
|
-
errors.push({
|
|
334
|
-
file: controller.file,
|
|
335
|
-
controller: controller.name,
|
|
336
|
-
function: name,
|
|
337
|
-
message: `binded arguments in the "path" between function's decorator and parameters' decorators are different (function: [${binded.join(
|
|
338
|
-
", ",
|
|
339
|
-
)}], parameters: [${parameters.join(", ")}]).`,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// RETURNS
|
|
344
|
-
if (errors.length) {
|
|
345
|
-
project.errors.push(...errors);
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
return meta;
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
/* ---------------------------------------------------------
|
|
352
|
-
PARAMETER
|
|
353
|
-
--------------------------------------------------------- */
|
|
354
|
-
function _Analyze_parameter(
|
|
355
|
-
key: string,
|
|
356
|
-
param: INestParam,
|
|
357
|
-
): IController.IParameter | null {
|
|
358
|
-
const symbol: string = key.split(":")[0];
|
|
359
|
-
if (symbol.indexOf("__custom") !== -1)
|
|
360
|
-
return _Analyze_custom_parameter(param);
|
|
361
|
-
|
|
362
|
-
const typeIndex: RouteParamtypes = Number(symbol[0]) as RouteParamtypes;
|
|
363
|
-
if (isNaN(typeIndex) === true) return null;
|
|
364
|
-
|
|
365
|
-
const type: ParamCategory | undefined = getNestParamType(typeIndex);
|
|
366
|
-
if (type === undefined) return null;
|
|
367
|
-
|
|
368
|
-
return {
|
|
369
|
-
custom: false,
|
|
370
|
-
name: key,
|
|
371
|
-
category: type,
|
|
372
|
-
index: param.index,
|
|
373
|
-
field: param.data,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function _Analyze_custom_parameter(
|
|
378
|
-
param: INestParam,
|
|
379
|
-
): IController.IParameter | null {
|
|
380
|
-
if (param.factory === undefined) return null;
|
|
381
|
-
else if (
|
|
382
|
-
param.factory.name === "EncryptedBody" ||
|
|
383
|
-
param.factory.name === "PlainBody" ||
|
|
384
|
-
param.factory.name === "TypedQueryBody" ||
|
|
385
|
-
param.factory.name === "TypedBody" ||
|
|
386
|
-
param.factory.name === "TypedFormDataBody"
|
|
387
|
-
)
|
|
388
|
-
return {
|
|
389
|
-
custom: true,
|
|
390
|
-
category: "body",
|
|
391
|
-
index: param.index,
|
|
392
|
-
name: param.name,
|
|
393
|
-
field: param.data,
|
|
394
|
-
encrypted: param.factory.name === "EncryptedBody",
|
|
395
|
-
contentType:
|
|
396
|
-
param.factory.name === "PlainBody" ||
|
|
397
|
-
param.factory.name === "EncryptedBody"
|
|
398
|
-
? "text/plain"
|
|
399
|
-
: param.factory.name === "TypedQueryBody"
|
|
400
|
-
? "application/x-www-form-urlencoded"
|
|
401
|
-
: param.factory.name === "TypedFormDataBody"
|
|
402
|
-
? "multipart/form-data"
|
|
403
|
-
: "application/json",
|
|
404
|
-
};
|
|
405
|
-
else if (param.factory.name === "TypedHeaders")
|
|
406
|
-
return {
|
|
407
|
-
custom: true,
|
|
408
|
-
category: "headers",
|
|
409
|
-
name: param.name,
|
|
410
|
-
index: param.index,
|
|
411
|
-
field: param.data,
|
|
412
|
-
};
|
|
413
|
-
else if (param.factory.name === "TypedParam")
|
|
414
|
-
return {
|
|
415
|
-
custom: true,
|
|
416
|
-
category: "param",
|
|
417
|
-
name: param.name,
|
|
418
|
-
index: param.index,
|
|
419
|
-
field: param.data,
|
|
420
|
-
};
|
|
421
|
-
else if (param.factory.name === "TypedQuery")
|
|
422
|
-
return {
|
|
423
|
-
custom: true,
|
|
424
|
-
name: param.name,
|
|
425
|
-
category: "query",
|
|
426
|
-
index: param.index,
|
|
427
|
-
field: undefined,
|
|
428
|
-
};
|
|
429
|
-
else return null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
type NestParameters = {
|
|
433
|
-
[key: string]: INestParam;
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
interface INestParam {
|
|
437
|
-
name: string;
|
|
438
|
-
index: number;
|
|
439
|
-
factory?: (...args: any) => any;
|
|
440
|
-
data: string | undefined;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// node_modules/@nestjs/common/lib/enums/request-method.enum.ts
|
|
445
|
-
const METHODS = [
|
|
446
|
-
"GET",
|
|
447
|
-
"POST",
|
|
448
|
-
"PUT",
|
|
449
|
-
"DELETE",
|
|
450
|
-
"PATCH",
|
|
451
|
-
"ALL",
|
|
452
|
-
"OPTIONS",
|
|
453
|
-
"HEAD",
|
|
454
|
-
];
|
|
455
|
-
|
|
456
|
-
// https://github.com/nestjs/nest/blob/master/packages/common/enums/route-paramtypes.enum.ts
|
|
457
|
-
const getNestParamType = (value: RouteParamtypes) => {
|
|
458
|
-
if (value === RouteParamtypes.BODY) return "body";
|
|
459
|
-
else if (value === RouteParamtypes.HEADERS) return "headers";
|
|
460
|
-
else if (value === RouteParamtypes.QUERY) return "query";
|
|
461
|
-
else if (value === RouteParamtypes.PARAM) return "param";
|
|
462
|
-
return undefined;
|
|
463
|
-
};
|
|
1
|
+
import * as Constants from "@nestjs/common/constants";
|
|
2
|
+
import { RouteParamtypes } from "@nestjs/common/enums/route-paramtypes.enum";
|
|
3
|
+
import { VERSION_NEUTRAL, VersionValue } from "@nestjs/common/interfaces";
|
|
4
|
+
import "reflect-metadata";
|
|
5
|
+
import { ranges } from "tstl";
|
|
6
|
+
|
|
7
|
+
import { IController } from "../structures/IController";
|
|
8
|
+
import { IErrorReport } from "../structures/IErrorReport";
|
|
9
|
+
import { INestiaProject } from "../structures/INestiaProject";
|
|
10
|
+
import { ParamCategory } from "../structures/ParamCategory";
|
|
11
|
+
import { ArrayUtil } from "../utils/ArrayUtil";
|
|
12
|
+
import { PathAnalyzer } from "./PathAnalyzer";
|
|
13
|
+
import { SecurityAnalyzer } from "./SecurityAnalyzer";
|
|
14
|
+
|
|
15
|
+
type IModule = {
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export namespace ReflectAnalyzer {
|
|
20
|
+
export const analyze =
|
|
21
|
+
(project: INestiaProject) =>
|
|
22
|
+
async (
|
|
23
|
+
unique: WeakSet<any>,
|
|
24
|
+
file: string,
|
|
25
|
+
prefixes: string[],
|
|
26
|
+
target?: Function,
|
|
27
|
+
): Promise<IController[]> => {
|
|
28
|
+
const module: IModule = await (async () => {
|
|
29
|
+
try {
|
|
30
|
+
return await import(file);
|
|
31
|
+
} catch (exp) {
|
|
32
|
+
console.log(
|
|
33
|
+
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
|
|
34
|
+
);
|
|
35
|
+
console.log(`Error on "${file}" file. Check your code.`);
|
|
36
|
+
console.log(exp);
|
|
37
|
+
console.log(
|
|
38
|
+
">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
|
|
39
|
+
);
|
|
40
|
+
process.exit(-1);
|
|
41
|
+
}
|
|
42
|
+
})();
|
|
43
|
+
const ret: IController[] = [];
|
|
44
|
+
|
|
45
|
+
for (const [key, value] of Object.entries(module)) {
|
|
46
|
+
if (typeof value !== "function" || unique.has(value)) continue;
|
|
47
|
+
else if ((target ?? value) !== value) continue;
|
|
48
|
+
else unique.add(value);
|
|
49
|
+
|
|
50
|
+
const result: IController | null = _Analyze_controller(project)(
|
|
51
|
+
file,
|
|
52
|
+
key,
|
|
53
|
+
value,
|
|
54
|
+
prefixes,
|
|
55
|
+
);
|
|
56
|
+
if (result !== null) ret.push(result);
|
|
57
|
+
}
|
|
58
|
+
return ret;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/* ---------------------------------------------------------
|
|
62
|
+
CONTROLLER
|
|
63
|
+
--------------------------------------------------------- */
|
|
64
|
+
const _Analyze_controller =
|
|
65
|
+
(project: INestiaProject) =>
|
|
66
|
+
(
|
|
67
|
+
file: string,
|
|
68
|
+
name: string,
|
|
69
|
+
creator: any,
|
|
70
|
+
prefixes: string[],
|
|
71
|
+
): IController | null => {
|
|
72
|
+
//----
|
|
73
|
+
// VALIDATIONS
|
|
74
|
+
//----
|
|
75
|
+
// MUST BE TYPE OF A CREATOR WHO HAS THE CONSTRUCTOR
|
|
76
|
+
if (
|
|
77
|
+
!(
|
|
78
|
+
creator instanceof Function && creator.constructor instanceof Function
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
return null;
|
|
82
|
+
// MUST HAVE THOSE MATADATA
|
|
83
|
+
else if (
|
|
84
|
+
ArrayUtil.has(
|
|
85
|
+
Reflect.getMetadataKeys(creator),
|
|
86
|
+
Constants.PATH_METADATA,
|
|
87
|
+
Constants.HOST_METADATA,
|
|
88
|
+
Constants.SCOPE_OPTIONS_METADATA,
|
|
89
|
+
) === false
|
|
90
|
+
)
|
|
91
|
+
return null;
|
|
92
|
+
|
|
93
|
+
//----
|
|
94
|
+
// CONSTRUCTION
|
|
95
|
+
//----
|
|
96
|
+
// BASIC INFO
|
|
97
|
+
const meta: IController = {
|
|
98
|
+
target: creator,
|
|
99
|
+
file,
|
|
100
|
+
name,
|
|
101
|
+
functions: [],
|
|
102
|
+
prefixes,
|
|
103
|
+
paths: _Get_paths(creator).filter((str) => {
|
|
104
|
+
if (str.includes("*") === true) {
|
|
105
|
+
project.warnings.push({
|
|
106
|
+
file,
|
|
107
|
+
controller: name,
|
|
108
|
+
function: null,
|
|
109
|
+
message: "@nestia/sdk does not compose wildcard controller.",
|
|
110
|
+
});
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}),
|
|
115
|
+
versions: _Get_versions(creator),
|
|
116
|
+
security: _Get_securities(creator),
|
|
117
|
+
swaggerTgas: Reflect.getMetadata("swagger/apiUseTags", creator) ?? [],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// PARSE CHILDREN DATA
|
|
121
|
+
for (const tuple of _Get_prototype_entries(creator)) {
|
|
122
|
+
const child: IController.IFunction | null = _Analyze_function(project)(
|
|
123
|
+
creator.prototype,
|
|
124
|
+
meta,
|
|
125
|
+
...tuple,
|
|
126
|
+
);
|
|
127
|
+
if (child !== null) meta.functions.push(child);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// RETURNS
|
|
131
|
+
return meta;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
function _Get_prototype_entries(creator: any): Array<[string, unknown]> {
|
|
135
|
+
const keyList = Object.getOwnPropertyNames(creator.prototype);
|
|
136
|
+
const entries: Array<[string, unknown]> = keyList.map((key) => [
|
|
137
|
+
key,
|
|
138
|
+
creator.prototype[key],
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const parent = Object.getPrototypeOf(creator);
|
|
142
|
+
if (parent.prototype !== undefined)
|
|
143
|
+
entries.push(..._Get_prototype_entries(parent));
|
|
144
|
+
|
|
145
|
+
return entries;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function _Get_paths(target: any): string[] {
|
|
149
|
+
const value: string | string[] = Reflect.getMetadata(
|
|
150
|
+
Constants.PATH_METADATA,
|
|
151
|
+
target,
|
|
152
|
+
);
|
|
153
|
+
if (typeof value === "string") return [value];
|
|
154
|
+
else if (value.length === 0) return [""];
|
|
155
|
+
else return value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function _Get_versions(
|
|
159
|
+
target: any,
|
|
160
|
+
):
|
|
161
|
+
| Array<Exclude<VersionValue, Array<string | typeof VERSION_NEUTRAL>>>
|
|
162
|
+
| undefined {
|
|
163
|
+
const value: VersionValue | undefined = Reflect.getMetadata(
|
|
164
|
+
Constants.VERSION_METADATA,
|
|
165
|
+
target,
|
|
166
|
+
);
|
|
167
|
+
return value === undefined || Array.isArray(value) ? value : [value];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function _Get_securities(value: any): Record<string, string[]>[] {
|
|
171
|
+
const entire: Record<string, string[]>[] | undefined = Reflect.getMetadata(
|
|
172
|
+
"swagger/apiSecurity",
|
|
173
|
+
value,
|
|
174
|
+
);
|
|
175
|
+
return entire ? SecurityAnalyzer.merge(...entire) : [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function _Get_exceptions(
|
|
179
|
+
value: any,
|
|
180
|
+
): Record<number | "2XX" | "3XX" | "4XX" | "5XX", IController.IException> {
|
|
181
|
+
const entire: IController.IException[] | undefined = Reflect.getMetadata(
|
|
182
|
+
"nestia/TypedException",
|
|
183
|
+
value,
|
|
184
|
+
);
|
|
185
|
+
return Object.fromEntries(
|
|
186
|
+
(entire ?? []).map((exp) => [exp.status, exp]),
|
|
187
|
+
) as Record<number | "2XX" | "3XX" | "4XX" | "5XX", IController.IException>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ---------------------------------------------------------
|
|
191
|
+
FUNCTION
|
|
192
|
+
--------------------------------------------------------- */
|
|
193
|
+
const _Analyze_function =
|
|
194
|
+
(project: INestiaProject) =>
|
|
195
|
+
(
|
|
196
|
+
classProto: any,
|
|
197
|
+
controller: IController,
|
|
198
|
+
name: string,
|
|
199
|
+
proto: any,
|
|
200
|
+
): IController.IFunction | null => {
|
|
201
|
+
//----
|
|
202
|
+
// VALIDATIONS
|
|
203
|
+
//----
|
|
204
|
+
// MUST BE TYPE OF A FUNCTION
|
|
205
|
+
if (!(proto instanceof Function)) return null;
|
|
206
|
+
// MUST HAVE THOSE METADATE
|
|
207
|
+
else if (
|
|
208
|
+
ArrayUtil.has(
|
|
209
|
+
Reflect.getMetadataKeys(proto),
|
|
210
|
+
Constants.PATH_METADATA,
|
|
211
|
+
Constants.METHOD_METADATA,
|
|
212
|
+
) === false
|
|
213
|
+
)
|
|
214
|
+
return null;
|
|
215
|
+
|
|
216
|
+
const errors: IErrorReport[] = [];
|
|
217
|
+
|
|
218
|
+
//----
|
|
219
|
+
// CONSTRUCTION
|
|
220
|
+
//----
|
|
221
|
+
// BASIC INFO
|
|
222
|
+
const encrypted: boolean =
|
|
223
|
+
Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
|
|
224
|
+
?.constructor?.name === "EncryptedRouteInterceptor";
|
|
225
|
+
const query: boolean =
|
|
226
|
+
Reflect.getMetadata(Constants.INTERCEPTORS_METADATA, proto)?.[0]
|
|
227
|
+
?.constructor?.name === "TypedQueryRouteInterceptor";
|
|
228
|
+
const method: string =
|
|
229
|
+
METHODS[Reflect.getMetadata(Constants.METHOD_METADATA, proto)];
|
|
230
|
+
if (method === undefined || method === "OPTIONS") return null;
|
|
231
|
+
|
|
232
|
+
const parameters: IController.IParameter[] = (() => {
|
|
233
|
+
const nestParameters: NestParameters | undefined = Reflect.getMetadata(
|
|
234
|
+
Constants.ROUTE_ARGS_METADATA,
|
|
235
|
+
classProto.constructor,
|
|
236
|
+
name,
|
|
237
|
+
);
|
|
238
|
+
if (nestParameters === undefined) return [];
|
|
239
|
+
|
|
240
|
+
const output: IController.IParameter[] = [];
|
|
241
|
+
for (const tuple of Object.entries(nestParameters)) {
|
|
242
|
+
const child: IController.IParameter | null = _Analyze_parameter(
|
|
243
|
+
...tuple,
|
|
244
|
+
);
|
|
245
|
+
if (child !== null) output.push(child);
|
|
246
|
+
}
|
|
247
|
+
return output.sort((x, y) => x.index - y.index);
|
|
248
|
+
})();
|
|
249
|
+
|
|
250
|
+
// VALIDATE BODY
|
|
251
|
+
const body: IController.IParameter | undefined = parameters.find(
|
|
252
|
+
(param) => param.category === "body",
|
|
253
|
+
);
|
|
254
|
+
if (body !== undefined && (method === "GET" || method === "HEAD")) {
|
|
255
|
+
project.errors.push({
|
|
256
|
+
file: controller.file,
|
|
257
|
+
controller: controller.name,
|
|
258
|
+
function: name,
|
|
259
|
+
message: `"body" parameter cannot be used in the "${method}" method.`,
|
|
260
|
+
});
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// DO CONSTRUCT
|
|
265
|
+
const meta: IController.IFunction = {
|
|
266
|
+
target: proto,
|
|
267
|
+
name,
|
|
268
|
+
method: method === "ALL" ? "POST" : method,
|
|
269
|
+
paths: _Get_paths(proto).filter((str) => {
|
|
270
|
+
if (str.includes("*") === true) {
|
|
271
|
+
project.warnings.push({
|
|
272
|
+
file: controller.file,
|
|
273
|
+
controller: controller.name,
|
|
274
|
+
function: name,
|
|
275
|
+
message: "@nestia/sdk does not compose wildcard method.",
|
|
276
|
+
});
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
return true;
|
|
280
|
+
}),
|
|
281
|
+
versions: _Get_versions(proto),
|
|
282
|
+
parameters,
|
|
283
|
+
status: Reflect.getMetadata(Constants.HTTP_CODE_METADATA, proto),
|
|
284
|
+
encrypted,
|
|
285
|
+
contentType: encrypted
|
|
286
|
+
? "text/plain"
|
|
287
|
+
: query
|
|
288
|
+
? "application/x-www-form-urlencoded"
|
|
289
|
+
: Reflect.getMetadata(Constants.HEADERS_METADATA, proto)?.find(
|
|
290
|
+
(h: Record<string, string>) =>
|
|
291
|
+
typeof h?.name === "string" &&
|
|
292
|
+
typeof h?.value === "string" &&
|
|
293
|
+
h.name.toLowerCase() === "content-type",
|
|
294
|
+
)?.value ?? "application/json",
|
|
295
|
+
security: _Get_securities(proto),
|
|
296
|
+
exceptions: _Get_exceptions(proto),
|
|
297
|
+
swaggerTags: [
|
|
298
|
+
...new Set([
|
|
299
|
+
...controller.swaggerTgas,
|
|
300
|
+
...(Reflect.getMetadata("swagger/apiUseTags", proto) ?? []),
|
|
301
|
+
]),
|
|
302
|
+
],
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// VALIDATE PATH ARGUMENTS
|
|
306
|
+
for (const controllerLocation of controller.paths)
|
|
307
|
+
for (const metaLocation of meta.paths) {
|
|
308
|
+
// NORMALIZE LOCATION
|
|
309
|
+
const location: string = PathAnalyzer.join(
|
|
310
|
+
controllerLocation,
|
|
311
|
+
metaLocation,
|
|
312
|
+
);
|
|
313
|
+
if (location.includes("*")) continue;
|
|
314
|
+
|
|
315
|
+
// LIST UP PARAMETERS
|
|
316
|
+
const binded: string[] | null = PathAnalyzer.parameters(location);
|
|
317
|
+
if (binded === null) {
|
|
318
|
+
project.errors.push({
|
|
319
|
+
file: controller.file,
|
|
320
|
+
controller: controller.name,
|
|
321
|
+
function: name,
|
|
322
|
+
message: `invalid path ("${location}")`,
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const parameters: string[] = meta.parameters
|
|
327
|
+
.filter((param) => param.category === "param")
|
|
328
|
+
.map((param) => param.field!)
|
|
329
|
+
.sort();
|
|
330
|
+
|
|
331
|
+
// DO VALIDATE
|
|
332
|
+
if (ranges.equal(binded.sort(), parameters) === false)
|
|
333
|
+
errors.push({
|
|
334
|
+
file: controller.file,
|
|
335
|
+
controller: controller.name,
|
|
336
|
+
function: name,
|
|
337
|
+
message: `binded arguments in the "path" between function's decorator and parameters' decorators are different (function: [${binded.join(
|
|
338
|
+
", ",
|
|
339
|
+
)}], parameters: [${parameters.join(", ")}]).`,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// RETURNS
|
|
344
|
+
if (errors.length) {
|
|
345
|
+
project.errors.push(...errors);
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return meta;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/* ---------------------------------------------------------
|
|
352
|
+
PARAMETER
|
|
353
|
+
--------------------------------------------------------- */
|
|
354
|
+
function _Analyze_parameter(
|
|
355
|
+
key: string,
|
|
356
|
+
param: INestParam,
|
|
357
|
+
): IController.IParameter | null {
|
|
358
|
+
const symbol: string = key.split(":")[0];
|
|
359
|
+
if (symbol.indexOf("__custom") !== -1)
|
|
360
|
+
return _Analyze_custom_parameter(param);
|
|
361
|
+
|
|
362
|
+
const typeIndex: RouteParamtypes = Number(symbol[0]) as RouteParamtypes;
|
|
363
|
+
if (isNaN(typeIndex) === true) return null;
|
|
364
|
+
|
|
365
|
+
const type: ParamCategory | undefined = getNestParamType(typeIndex);
|
|
366
|
+
if (type === undefined) return null;
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
custom: false,
|
|
370
|
+
name: key,
|
|
371
|
+
category: type,
|
|
372
|
+
index: param.index,
|
|
373
|
+
field: param.data,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function _Analyze_custom_parameter(
|
|
378
|
+
param: INestParam,
|
|
379
|
+
): IController.IParameter | null {
|
|
380
|
+
if (param.factory === undefined) return null;
|
|
381
|
+
else if (
|
|
382
|
+
param.factory.name === "EncryptedBody" ||
|
|
383
|
+
param.factory.name === "PlainBody" ||
|
|
384
|
+
param.factory.name === "TypedQueryBody" ||
|
|
385
|
+
param.factory.name === "TypedBody" ||
|
|
386
|
+
param.factory.name === "TypedFormDataBody"
|
|
387
|
+
)
|
|
388
|
+
return {
|
|
389
|
+
custom: true,
|
|
390
|
+
category: "body",
|
|
391
|
+
index: param.index,
|
|
392
|
+
name: param.name,
|
|
393
|
+
field: param.data,
|
|
394
|
+
encrypted: param.factory.name === "EncryptedBody",
|
|
395
|
+
contentType:
|
|
396
|
+
param.factory.name === "PlainBody" ||
|
|
397
|
+
param.factory.name === "EncryptedBody"
|
|
398
|
+
? "text/plain"
|
|
399
|
+
: param.factory.name === "TypedQueryBody"
|
|
400
|
+
? "application/x-www-form-urlencoded"
|
|
401
|
+
: param.factory.name === "TypedFormDataBody"
|
|
402
|
+
? "multipart/form-data"
|
|
403
|
+
: "application/json",
|
|
404
|
+
};
|
|
405
|
+
else if (param.factory.name === "TypedHeaders")
|
|
406
|
+
return {
|
|
407
|
+
custom: true,
|
|
408
|
+
category: "headers",
|
|
409
|
+
name: param.name,
|
|
410
|
+
index: param.index,
|
|
411
|
+
field: param.data,
|
|
412
|
+
};
|
|
413
|
+
else if (param.factory.name === "TypedParam")
|
|
414
|
+
return {
|
|
415
|
+
custom: true,
|
|
416
|
+
category: "param",
|
|
417
|
+
name: param.name,
|
|
418
|
+
index: param.index,
|
|
419
|
+
field: param.data,
|
|
420
|
+
};
|
|
421
|
+
else if (param.factory.name === "TypedQuery")
|
|
422
|
+
return {
|
|
423
|
+
custom: true,
|
|
424
|
+
name: param.name,
|
|
425
|
+
category: "query",
|
|
426
|
+
index: param.index,
|
|
427
|
+
field: undefined,
|
|
428
|
+
};
|
|
429
|
+
else return null;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
type NestParameters = {
|
|
433
|
+
[key: string]: INestParam;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
interface INestParam {
|
|
437
|
+
name: string;
|
|
438
|
+
index: number;
|
|
439
|
+
factory?: (...args: any) => any;
|
|
440
|
+
data: string | undefined;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// node_modules/@nestjs/common/lib/enums/request-method.enum.ts
|
|
445
|
+
const METHODS = [
|
|
446
|
+
"GET",
|
|
447
|
+
"POST",
|
|
448
|
+
"PUT",
|
|
449
|
+
"DELETE",
|
|
450
|
+
"PATCH",
|
|
451
|
+
"ALL",
|
|
452
|
+
"OPTIONS",
|
|
453
|
+
"HEAD",
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
// https://github.com/nestjs/nest/blob/master/packages/common/enums/route-paramtypes.enum.ts
|
|
457
|
+
const getNestParamType = (value: RouteParamtypes) => {
|
|
458
|
+
if (value === RouteParamtypes.BODY) return "body";
|
|
459
|
+
else if (value === RouteParamtypes.HEADERS) return "headers";
|
|
460
|
+
else if (value === RouteParamtypes.QUERY) return "query";
|
|
461
|
+
else if (value === RouteParamtypes.PARAM) return "param";
|
|
462
|
+
return undefined;
|
|
463
|
+
};
|