@nestia/sdk 2.0.0-dev.20230831-4 → 2.0.0-dev.20230901
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/assets/bundle/api/utils/NestiaSimulator.ts +1 -23
- package/lib/NestiaSdkApplication.js +1 -1
- package/lib/NestiaSdkApplication.js.map +1 -1
- package/lib/analyses/ControllerAnalyzer.js +11 -5
- package/lib/analyses/ControllerAnalyzer.js.map +1 -1
- package/lib/analyses/ExceptionAnalyzer.js +7 -2
- package/lib/analyses/ExceptionAnalyzer.js.map +1 -1
- package/lib/analyses/ImportAnalyzer.js +1 -1
- package/lib/analyses/ImportAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectAnalyzer.js +0 -10
- package/lib/analyses/ReflectAnalyzer.js.map +1 -1
- package/lib/executable/internal/NestiaSdkConfig.js +37 -46
- package/lib/executable/internal/NestiaSdkConfig.js.map +1 -1
- package/lib/generates/SwaggerGenerator.d.ts +10 -0
- package/lib/generates/SwaggerGenerator.js +29 -497
- package/lib/generates/SwaggerGenerator.js.map +1 -1
- package/lib/generates/internal/E2eFileProgrammer.js +5 -44
- package/lib/generates/internal/E2eFileProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkFunctionProgrammer.js +12 -10
- package/lib/generates/internal/SdkFunctionProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkSimulationProgrammer.js +4 -4
- package/lib/generates/internal/SdkSimulationProgrammer.js.map +1 -1
- package/lib/generates/internal/SwaggerSchemaGenerator.d.ts +19 -0
- package/lib/generates/internal/SwaggerSchemaGenerator.js +301 -0
- package/lib/generates/internal/SwaggerSchemaGenerator.js.map +1 -0
- package/lib/generates/internal/SwaggerSchemaValidator.d.ts +7 -0
- package/lib/generates/internal/SwaggerSchemaValidator.js +200 -0
- package/lib/generates/internal/SwaggerSchemaValidator.js.map +1 -0
- package/lib/structures/IController.d.ts +0 -4
- package/lib/structures/IRoute.d.ts +6 -3
- package/lib/structures/ISwaggerError.d.ts +6 -0
- package/lib/structures/ISwaggerError.js +3 -0
- package/lib/structures/ISwaggerError.js.map +1 -0
- package/lib/structures/ISwaggerRoute.d.ts +2 -1
- package/lib/structures/ISwaggerSchemaTuple.d.ts +6 -0
- package/lib/structures/ISwaggerSchemaTuple.js +3 -0
- package/lib/structures/ISwaggerSchemaTuple.js.map +1 -0
- package/lib/structures/ITypeTuple.d.ts +1 -1
- package/package.json +5 -5
- package/src/NestiaSdkApplication.ts +1 -1
- package/src/analyses/ControllerAnalyzer.ts +13 -4
- package/src/analyses/ExceptionAnalyzer.ts +3 -2
- package/src/analyses/ImportAnalyzer.ts +1 -1
- package/src/analyses/ReflectAnalyzer.ts +0 -10
- package/src/generates/SwaggerGenerator.ts +86 -478
- package/src/generates/internal/E2eFileProgrammer.ts +7 -49
- package/src/generates/internal/SdkFunctionProgrammer.ts +13 -11
- package/src/generates/internal/SdkSimulationProgrammer.ts +5 -5
- package/src/generates/internal/SwaggerSchemaGenerator.ts +433 -0
- package/src/generates/internal/SwaggerSchemaValidator.ts +222 -0
- package/src/structures/IController.ts +0 -4
- package/src/structures/IRoute.ts +6 -3
- package/src/structures/ISwaggerError.ts +8 -0
- package/src/structures/ISwaggerRoute.ts +2 -1
- package/src/structures/ISwaggerSchemaTuple.ts +7 -0
- package/src/structures/ITypeTuple.ts +1 -1
|
@@ -19,15 +19,6 @@ export namespace E2eFileProgrammer {
|
|
|
19
19
|
instance,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
const additional: string[] = [];
|
|
23
|
-
for (const param of route.parameters.filter(
|
|
24
|
-
(p) => p.category !== "headers",
|
|
25
|
-
)) {
|
|
26
|
-
const type = getAdditional(param);
|
|
27
|
-
if (type === "uuid") additional.push(UUID);
|
|
28
|
-
else if (type === "date") additional.push(DATE);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
22
|
importer.internal({
|
|
32
23
|
type: false,
|
|
33
24
|
file: props.api,
|
|
@@ -39,7 +30,6 @@ export namespace E2eFileProgrammer {
|
|
|
39
30
|
importer.toScript(props.current),
|
|
40
31
|
"",
|
|
41
32
|
body,
|
|
42
|
-
...(additional.length ? ["", ...additional] : []),
|
|
43
33
|
].join("\n");
|
|
44
34
|
|
|
45
35
|
await fs.promises.writeFile(
|
|
@@ -53,7 +43,7 @@ export namespace E2eFileProgrammer {
|
|
|
53
43
|
(config: INestiaConfig) =>
|
|
54
44
|
(importer: ImportDictionary) =>
|
|
55
45
|
(route: IRoute): string => {
|
|
56
|
-
const tab: number = route.output.
|
|
46
|
+
const tab: number = route.output.typeName === "void" ? 2 : 3;
|
|
57
47
|
const headers = route.parameters.find(
|
|
58
48
|
(p) => p.category === "headers" && p.field === undefined,
|
|
59
49
|
);
|
|
@@ -67,7 +57,7 @@ export namespace E2eFileProgrammer {
|
|
|
67
57
|
" ...(connection.headers ?? {}),",
|
|
68
58
|
` ...${SdkImportWizard.typia(
|
|
69
59
|
importer,
|
|
70
|
-
)}.random<${headers.
|
|
60
|
+
)}.random<${headers.typeName}>(),`,
|
|
71
61
|
" },",
|
|
72
62
|
"},",
|
|
73
63
|
]
|
|
@@ -83,11 +73,11 @@ export namespace E2eFileProgrammer {
|
|
|
83
73
|
`export const ${name(route)} = async (`,
|
|
84
74
|
` connection: api.IConnection`,
|
|
85
75
|
`): Promise<void> => {`,
|
|
86
|
-
...(route.output.
|
|
76
|
+
...(route.output.typeName === "void"
|
|
87
77
|
? [` ${output}`]
|
|
88
78
|
: [
|
|
89
79
|
` const output: ${primitive(config)(importer)(
|
|
90
|
-
route.output.
|
|
80
|
+
route.output.typeName,
|
|
91
81
|
)} = `,
|
|
92
82
|
` ${output}`,
|
|
93
83
|
` ${SdkImportWizard.typia(
|
|
@@ -103,16 +93,9 @@ export namespace E2eFileProgrammer {
|
|
|
103
93
|
(importer: ImportDictionary) =>
|
|
104
94
|
(tab: number) =>
|
|
105
95
|
(param: IRoute.IParameter): string => {
|
|
106
|
-
const middle: string =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
(param.meta?.type === "uuid" || param.meta?.type === "date")
|
|
110
|
-
? param.meta.nullable
|
|
111
|
-
? `Math.random() < .2 ? null : ${param.meta.type}()`
|
|
112
|
-
: `${param.meta.type}()`
|
|
113
|
-
: `${SdkImportWizard.typia(importer)}.random<${primitive(
|
|
114
|
-
config,
|
|
115
|
-
)(importer)(param.type.name)}>()`;
|
|
96
|
+
const middle: string = `${SdkImportWizard.typia(
|
|
97
|
+
importer,
|
|
98
|
+
)}.random<${primitive(config)(importer)(param.typeName)}>()`;
|
|
116
99
|
return `${" ".repeat(4 * tab)}${middle},`;
|
|
117
100
|
};
|
|
118
101
|
|
|
@@ -129,29 +112,4 @@ export namespace E2eFileProgrammer {
|
|
|
129
112
|
config.primitive !== false
|
|
130
113
|
? `${SdkImportWizard.Primitive(importer)}<${name}>`
|
|
131
114
|
: name;
|
|
132
|
-
|
|
133
|
-
const getAdditional = (
|
|
134
|
-
param: IRoute.IParameter,
|
|
135
|
-
): "uuid" | "date" | null => {
|
|
136
|
-
if (param.custom === false || param.category !== "param" || !param.meta)
|
|
137
|
-
return null;
|
|
138
|
-
else if (param.meta.type === "uuid") return "uuid";
|
|
139
|
-
else if (param.meta.type === "date") return "date";
|
|
140
|
-
return null;
|
|
141
|
-
};
|
|
142
115
|
}
|
|
143
|
-
|
|
144
|
-
const UUID = `const uuid = (): string =>
|
|
145
|
-
"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
146
|
-
const r = (Math.random() * 16) | 0;
|
|
147
|
-
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
148
|
-
return v.toString(16);
|
|
149
|
-
});`;
|
|
150
|
-
const DATE = `const date = (): string => {
|
|
151
|
-
const date: Date = new Date(Math.floor(Math.random() * Date.now() * 2));
|
|
152
|
-
return [
|
|
153
|
-
date.getFullYear(),
|
|
154
|
-
(date.getMonth() + 1).toString().padStart(2, "0"),
|
|
155
|
-
date.getDate().toString().padStart(2, "0"),
|
|
156
|
-
].join("-");
|
|
157
|
-
}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Pair } from "tstl/utility/Pair";
|
|
2
2
|
|
|
3
|
-
import { IJsDocTagInfo } from "typia/lib/metadata/IJsDocTagInfo";
|
|
3
|
+
import { IJsDocTagInfo } from "typia/lib/schemas/metadata/IJsDocTagInfo";
|
|
4
4
|
import { Escaper } from "typia/lib/utils/Escaper";
|
|
5
5
|
|
|
6
6
|
import { INestiaConfig } from "../../INestiaConfig";
|
|
@@ -283,7 +283,7 @@ export namespace SdkFunctionProgrammer {
|
|
|
283
283
|
? `${route.name}.${
|
|
284
284
|
param === props.query ? "Query" : "Input"
|
|
285
285
|
}`
|
|
286
|
-
: param.
|
|
286
|
+
: param.typeName;
|
|
287
287
|
return `${param.name}${
|
|
288
288
|
param.optional ? "?" : ""
|
|
289
289
|
}: ${type}`;
|
|
@@ -292,7 +292,9 @@ export namespace SdkFunctionProgrammer {
|
|
|
292
292
|
|
|
293
293
|
// OUTPUT TYPE
|
|
294
294
|
const output: string =
|
|
295
|
-
route.output.
|
|
295
|
+
route.output.typeName === "void"
|
|
296
|
+
? "void"
|
|
297
|
+
: `${route.name}.Output`;
|
|
296
298
|
|
|
297
299
|
// RETURNS WITH CONSTRUCTION
|
|
298
300
|
return (
|
|
@@ -319,13 +321,13 @@ export namespace SdkFunctionProgrammer {
|
|
|
319
321
|
// LIST UP TYPES
|
|
320
322
|
const types: Pair<string, string>[] = [];
|
|
321
323
|
if (props.headers !== undefined)
|
|
322
|
-
types.push(new Pair("Headers", props.headers.
|
|
324
|
+
types.push(new Pair("Headers", props.headers.typeName));
|
|
323
325
|
if (props.query !== undefined)
|
|
324
|
-
types.push(new Pair("Query", props.query.
|
|
326
|
+
types.push(new Pair("Query", props.query.typeName));
|
|
325
327
|
if (props.input !== undefined)
|
|
326
|
-
types.push(new Pair("Input", props.input.
|
|
327
|
-
if (route.output.
|
|
328
|
-
types.push(new Pair("Output", route.output.
|
|
328
|
+
types.push(new Pair("Input", props.input.typeName));
|
|
329
|
+
if (route.output.typeName !== "void")
|
|
330
|
+
types.push(new Pair("Output", route.output.typeName));
|
|
329
331
|
|
|
330
332
|
// PATH WITH PARAMETERS
|
|
331
333
|
const parameters: IRoute.IParameter[] = filter_path_parameters(
|
|
@@ -397,15 +399,15 @@ export namespace SdkFunctionProgrammer {
|
|
|
397
399
|
(param) =>
|
|
398
400
|
`${param.name}: ${
|
|
399
401
|
param.category === "query" &&
|
|
400
|
-
param.
|
|
402
|
+
param.typeName === props.query?.typeName
|
|
401
403
|
? `${route.name}.Query`
|
|
402
|
-
: param.
|
|
404
|
+
: param.typeName
|
|
403
405
|
}`,
|
|
404
406
|
)
|
|
405
407
|
.join(", ")}): string => {\n` +
|
|
406
408
|
`${path};\n` +
|
|
407
409
|
` }\n` +
|
|
408
|
-
(config.simulate === true && route.output.
|
|
410
|
+
(config.simulate === true && route.output.typeName !== "void"
|
|
409
411
|
? ` export const random = (g?: Partial<${SdkImportWizard.typia(
|
|
410
412
|
importer,
|
|
411
413
|
)}.IRandomGenerator>): Output =>\n` +
|
|
@@ -8,7 +8,7 @@ export namespace SdkSimulationProgrammer {
|
|
|
8
8
|
(config: INestiaConfig) =>
|
|
9
9
|
(importer: ImportDictionary) =>
|
|
10
10
|
(route: IRoute): string => {
|
|
11
|
-
const output: boolean = route.output.
|
|
11
|
+
const output: boolean = route.output.typeName !== "void";
|
|
12
12
|
const returns = () => [
|
|
13
13
|
`return random(`,
|
|
14
14
|
` typeof connection.simulate === 'object' &&`,
|
|
@@ -28,7 +28,7 @@ export namespace SdkSimulationProgrammer {
|
|
|
28
28
|
`export const simulate = async (`,
|
|
29
29
|
` ${
|
|
30
30
|
route.parameters.filter((p) => p.category !== "headers")
|
|
31
|
-
.length === 0 && route.output.
|
|
31
|
+
.length === 0 && route.output.typeName === "void"
|
|
32
32
|
? "_connection"
|
|
33
33
|
: "connection"
|
|
34
34
|
}: ${
|
|
@@ -52,7 +52,7 @@ export namespace SdkSimulationProgrammer {
|
|
|
52
52
|
? "Query"
|
|
53
53
|
: "Input"
|
|
54
54
|
}`
|
|
55
|
-
: p.
|
|
55
|
+
: p.typeName
|
|
56
56
|
},`,
|
|
57
57
|
),
|
|
58
58
|
`): Promise<${output ? "Output" : "void"}> => {`,
|
|
@@ -97,8 +97,8 @@ export namespace SdkSimulationProgrammer {
|
|
|
97
97
|
? `assert.headers(() => ${SdkImportWizard.typia(
|
|
98
98
|
importer,
|
|
99
99
|
)}.assert(connection.headers);` // not reachable
|
|
100
|
-
: `assert.param("${
|
|
101
|
-
p.
|
|
100
|
+
: `assert.param("${
|
|
101
|
+
p.field
|
|
102
102
|
}")(() => ${SdkImportWizard.typia(
|
|
103
103
|
importer,
|
|
104
104
|
)}.assert(${p.name}));`,
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { Singleton } from "tstl";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
import { IJsonSchema } from "typia";
|
|
5
|
+
import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
|
|
6
|
+
import { MetadataFactory } from "typia/lib/factories/MetadataFactory";
|
|
7
|
+
import { Metadata } from "typia/lib/schemas/metadata/Metadata";
|
|
8
|
+
import { ValidationPipe } from "typia/lib/typings/ValidationPipe";
|
|
9
|
+
|
|
10
|
+
import { INestiaConfig } from "../../INestiaConfig";
|
|
11
|
+
import { IRoute } from "../../structures/IRoute";
|
|
12
|
+
import { ISwaggerError } from "../../structures/ISwaggerError";
|
|
13
|
+
import { ISwaggerRoute } from "../../structures/ISwaggerRoute";
|
|
14
|
+
import { ISwaggerSchemaTuple } from "../../structures/ISwaggerSchemaTuple";
|
|
15
|
+
import { SwaggerSchemaValidator } from "./SwaggerSchemaValidator";
|
|
16
|
+
|
|
17
|
+
export namespace SwaggerSchemaGenerator {
|
|
18
|
+
export interface IProps {
|
|
19
|
+
config: INestiaConfig.ISwaggerConfig;
|
|
20
|
+
checker: ts.TypeChecker;
|
|
21
|
+
collection: MetadataCollection;
|
|
22
|
+
tuples: Array<ISwaggerSchemaTuple>;
|
|
23
|
+
errors: ISwaggerError[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const response =
|
|
27
|
+
(props: IProps) =>
|
|
28
|
+
(route: IRoute): ISwaggerRoute.IResponseBody => {
|
|
29
|
+
const output: ISwaggerRoute.IResponseBody = {};
|
|
30
|
+
|
|
31
|
+
//----
|
|
32
|
+
// EXCEPTION STATUSES
|
|
33
|
+
//----
|
|
34
|
+
// FROM DECORATOR
|
|
35
|
+
for (const [status, exp] of Object.entries(route.exceptions)) {
|
|
36
|
+
const result = MetadataFactory.analyze(props.checker)({
|
|
37
|
+
escape: true,
|
|
38
|
+
constant: true,
|
|
39
|
+
absorb: false,
|
|
40
|
+
validate: (meta) => {
|
|
41
|
+
const bigint: boolean =
|
|
42
|
+
meta.atomics.some((a) => a.type === "bigint") ||
|
|
43
|
+
meta.constants.some((a) => a.type === "bigint");
|
|
44
|
+
return bigint ? ["bigint type is not allowed."] : [];
|
|
45
|
+
},
|
|
46
|
+
})(props.collection)(exp.type);
|
|
47
|
+
if (result.success === false)
|
|
48
|
+
props.errors.push(
|
|
49
|
+
...result.errors.map((e) => ({
|
|
50
|
+
...e,
|
|
51
|
+
route,
|
|
52
|
+
from: `response body (status: ${status})`,
|
|
53
|
+
})),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
output[status] = {
|
|
57
|
+
description: exp.description ?? "",
|
|
58
|
+
content: {
|
|
59
|
+
"application/json": {
|
|
60
|
+
schema: coalesceSchema(props)(result),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// FROM COMMENT TAGS -> ANY
|
|
67
|
+
for (const tag of route.tags) {
|
|
68
|
+
if (tag.name !== "throw" && tag.name !== "throws") continue;
|
|
69
|
+
|
|
70
|
+
const text: string | undefined = tag.text?.find(
|
|
71
|
+
(elem) => elem.kind === "text",
|
|
72
|
+
)?.text;
|
|
73
|
+
if (text === undefined) continue;
|
|
74
|
+
|
|
75
|
+
const elements: string[] = text
|
|
76
|
+
.split(" ")
|
|
77
|
+
.map((str) => str.trim());
|
|
78
|
+
const status: string = elements[0];
|
|
79
|
+
if (
|
|
80
|
+
isNaN(Number(status)) &&
|
|
81
|
+
status !== "2XX" &&
|
|
82
|
+
status !== "3XX" &&
|
|
83
|
+
status !== "4XX" &&
|
|
84
|
+
status !== "5XX"
|
|
85
|
+
)
|
|
86
|
+
continue;
|
|
87
|
+
|
|
88
|
+
const description: string | undefined =
|
|
89
|
+
elements.length === 1
|
|
90
|
+
? undefined
|
|
91
|
+
: elements.slice(1).join(" ");
|
|
92
|
+
const oldbie = output[status];
|
|
93
|
+
if (description && oldbie !== undefined)
|
|
94
|
+
oldbie.description = description;
|
|
95
|
+
else if (oldbie === undefined)
|
|
96
|
+
output[status] = {
|
|
97
|
+
description: description ?? "",
|
|
98
|
+
content: {
|
|
99
|
+
"application/json": {
|
|
100
|
+
schema: {},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//----
|
|
107
|
+
// SUCCESS
|
|
108
|
+
//----
|
|
109
|
+
// STATUS
|
|
110
|
+
const status: string =
|
|
111
|
+
route.status !== undefined
|
|
112
|
+
? String(route.status)
|
|
113
|
+
: route.method === "GET" ||
|
|
114
|
+
route.method === "HEAD" ||
|
|
115
|
+
route.method === "DELETE"
|
|
116
|
+
? "200"
|
|
117
|
+
: "201";
|
|
118
|
+
|
|
119
|
+
// SCHEMA
|
|
120
|
+
const result = MetadataFactory.analyze(props.checker)({
|
|
121
|
+
escape: true,
|
|
122
|
+
constant: true,
|
|
123
|
+
absorb: false,
|
|
124
|
+
validate: (meta) => {
|
|
125
|
+
const bigint: boolean =
|
|
126
|
+
meta.atomics.some((a) => a.type === "bigint") ||
|
|
127
|
+
meta.constants.some((a) => a.type === "bigint");
|
|
128
|
+
return bigint ? ["bigint type is not allowed."] : [];
|
|
129
|
+
},
|
|
130
|
+
})(props.collection)(route.output.type);
|
|
131
|
+
if (result.success === false)
|
|
132
|
+
props.errors.push(
|
|
133
|
+
...result.errors.map((e) => ({
|
|
134
|
+
...e,
|
|
135
|
+
route,
|
|
136
|
+
from: `response body (status: ${status})`,
|
|
137
|
+
})),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// DO ASSIGN
|
|
141
|
+
const description =
|
|
142
|
+
describe(route, "return") ?? describe(route, "returns");
|
|
143
|
+
output[status] = {
|
|
144
|
+
description: route.encrypted
|
|
145
|
+
? `${warning
|
|
146
|
+
.get(!!description)
|
|
147
|
+
.get("response", route.method)}${description ?? ""}`
|
|
148
|
+
: description ?? "",
|
|
149
|
+
content:
|
|
150
|
+
route.output.typeName === "void"
|
|
151
|
+
? undefined
|
|
152
|
+
: {
|
|
153
|
+
[route.output.contentType]: {
|
|
154
|
+
schema: coalesceSchema(props)(result),
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
"x-nestia-encrypted": route.encrypted,
|
|
158
|
+
};
|
|
159
|
+
return output;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const body =
|
|
163
|
+
(props: IProps) =>
|
|
164
|
+
(route: IRoute) =>
|
|
165
|
+
(param: IRoute.IParameter): ISwaggerRoute.IRequestBody => {
|
|
166
|
+
// ANALZE TYPE WITH VALIDATION
|
|
167
|
+
const result = MetadataFactory.analyze(props.checker)({
|
|
168
|
+
escape: true,
|
|
169
|
+
constant: true,
|
|
170
|
+
absorb: true,
|
|
171
|
+
validate: (meta) => {
|
|
172
|
+
const bigint: boolean =
|
|
173
|
+
meta.atomics.some((a) => a.type === "bigint") ||
|
|
174
|
+
meta.constants.some((a) => a.type === "bigint");
|
|
175
|
+
return bigint ? ["bigint type is not allowed."] : [];
|
|
176
|
+
},
|
|
177
|
+
})(props.collection)(param.type);
|
|
178
|
+
if (result.success === false)
|
|
179
|
+
props.errors.push(
|
|
180
|
+
...result.errors.map((e) => ({
|
|
181
|
+
...e,
|
|
182
|
+
route,
|
|
183
|
+
from: `body parameter ${param.name}`,
|
|
184
|
+
})),
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// LIST UP PROPERTIES
|
|
188
|
+
const contentType =
|
|
189
|
+
param.custom && param.category === "body"
|
|
190
|
+
? param.contentType
|
|
191
|
+
: "application/json";
|
|
192
|
+
const encrypted: boolean =
|
|
193
|
+
param.custom && param.category === "body" && param.encrypted;
|
|
194
|
+
const description: string | undefined = describe(
|
|
195
|
+
route,
|
|
196
|
+
"param",
|
|
197
|
+
param.name,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// RETURNS WITH LAZY CONSTRUCTION
|
|
201
|
+
const schema: IJsonSchema = coalesceSchema(props)(result);
|
|
202
|
+
return {
|
|
203
|
+
description: encrypted
|
|
204
|
+
? `${warning.get(!!description).get("request")}${
|
|
205
|
+
description ?? ""
|
|
206
|
+
}`
|
|
207
|
+
: description,
|
|
208
|
+
content: {
|
|
209
|
+
[contentType]: {
|
|
210
|
+
schema,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
required: true,
|
|
214
|
+
"x-nestia-encrypted": encrypted,
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const parameter =
|
|
219
|
+
(props: IProps) =>
|
|
220
|
+
(route: IRoute) =>
|
|
221
|
+
(param: IRoute.IParameter): ISwaggerRoute.IParameter =>
|
|
222
|
+
param.category === "headers"
|
|
223
|
+
? headers(props)(route)(param)
|
|
224
|
+
: param.category === "param"
|
|
225
|
+
? path(props)(route)(param)
|
|
226
|
+
: query(props)(route)(param);
|
|
227
|
+
|
|
228
|
+
const headers =
|
|
229
|
+
(props: IProps) =>
|
|
230
|
+
(route: IRoute) =>
|
|
231
|
+
(param: IRoute.IParameter): ISwaggerRoute.IParameter => {
|
|
232
|
+
// ANALYZE TYPE WITH VALIDATIONS
|
|
233
|
+
const result = (() => {
|
|
234
|
+
const result = MetadataFactory.analyze(props.checker)({
|
|
235
|
+
escape: false,
|
|
236
|
+
constant: true,
|
|
237
|
+
absorb: true,
|
|
238
|
+
})(props.collection)(param.type);
|
|
239
|
+
if (result.success === false)
|
|
240
|
+
props.errors.push(
|
|
241
|
+
...result.errors.map((e) => ({
|
|
242
|
+
...e,
|
|
243
|
+
route,
|
|
244
|
+
from: `header parameter ${param.name}`,
|
|
245
|
+
})),
|
|
246
|
+
);
|
|
247
|
+
else if (result.data.objects.length === 1) {
|
|
248
|
+
// WHEN OBJECT CASE, VALIDATE IT MORE DETAILY
|
|
249
|
+
const again = MetadataFactory.analyze(props.checker)({
|
|
250
|
+
escape: false,
|
|
251
|
+
constant: true,
|
|
252
|
+
absorb: true,
|
|
253
|
+
validate: SwaggerSchemaValidator.headers,
|
|
254
|
+
})(new MetadataCollection())(param.type);
|
|
255
|
+
if (again.success === false) return again;
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
})();
|
|
259
|
+
|
|
260
|
+
// RETURNS WITH LAZY CONSTRUCTION
|
|
261
|
+
return lazyParameterReturns(props)(route)("header")(param, result);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const path =
|
|
265
|
+
(props: IProps) =>
|
|
266
|
+
(route: IRoute) =>
|
|
267
|
+
(param: IRoute.IParameter): ISwaggerRoute.IParameter => {
|
|
268
|
+
// ANALZE TYPE WITH VALIDATION
|
|
269
|
+
const result = MetadataFactory.analyze(props.checker)({
|
|
270
|
+
escape: false,
|
|
271
|
+
constant: true,
|
|
272
|
+
absorb: true,
|
|
273
|
+
validate: SwaggerSchemaValidator.path,
|
|
274
|
+
})(props.collection)(param.type);
|
|
275
|
+
if (result.success === false)
|
|
276
|
+
props.errors.push(
|
|
277
|
+
...result.errors.map((e) => ({
|
|
278
|
+
...e,
|
|
279
|
+
route,
|
|
280
|
+
from: `path parameter ${param.name}`,
|
|
281
|
+
})),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// RETURNS WITH LAZY CONSTRUCTION
|
|
285
|
+
return lazyParameterReturns(props)(route)("path")(param, result);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const query =
|
|
289
|
+
(props: IProps) =>
|
|
290
|
+
(route: IRoute) =>
|
|
291
|
+
(param: IRoute.IParameter): ISwaggerRoute.IParameter => {
|
|
292
|
+
// ANALYZE TYPE WITH VALIDATIONS
|
|
293
|
+
const result = (() => {
|
|
294
|
+
const result = MetadataFactory.analyze(props.checker)({
|
|
295
|
+
escape: false,
|
|
296
|
+
constant: true,
|
|
297
|
+
absorb: true,
|
|
298
|
+
})(props.collection)(param.type);
|
|
299
|
+
if (result.success === false) {
|
|
300
|
+
props.errors.push(
|
|
301
|
+
...result.errors.map((e) => ({
|
|
302
|
+
...e,
|
|
303
|
+
route,
|
|
304
|
+
from: `query parameter ${param.name}`,
|
|
305
|
+
})),
|
|
306
|
+
);
|
|
307
|
+
} else if (result.data.objects.length === 1) {
|
|
308
|
+
// WHEN OBJECT CASE, VALIDATE IT MORE DETAILY
|
|
309
|
+
const again = MetadataFactory.analyze(props.checker)({
|
|
310
|
+
escape: false,
|
|
311
|
+
constant: true,
|
|
312
|
+
absorb: true,
|
|
313
|
+
validate: SwaggerSchemaValidator.query,
|
|
314
|
+
})(new MetadataCollection())(param.type);
|
|
315
|
+
if (again.success === false) return again;
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
})();
|
|
319
|
+
|
|
320
|
+
// RETURNS WITH LAZY CONSTRUCTION
|
|
321
|
+
return lazyParameterReturns(props)(route)("query")(param, result);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const coalesceSchema =
|
|
325
|
+
(props: IProps) =>
|
|
326
|
+
(
|
|
327
|
+
result: ValidationPipe<Metadata, MetadataFactory.IError>,
|
|
328
|
+
): IJsonSchema => {
|
|
329
|
+
const schema: IJsonSchema = {} as any;
|
|
330
|
+
props.tuples.push({
|
|
331
|
+
metadata: result.success ? result.data : any.get(),
|
|
332
|
+
schema,
|
|
333
|
+
});
|
|
334
|
+
return schema;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const lazyParameterReturns =
|
|
338
|
+
(props: IProps) =>
|
|
339
|
+
(route: IRoute) =>
|
|
340
|
+
(inKeyword: string) =>
|
|
341
|
+
(
|
|
342
|
+
param: IRoute.IParameter,
|
|
343
|
+
result: ValidationPipe<Metadata, MetadataFactory.IError>,
|
|
344
|
+
) => {
|
|
345
|
+
const schema: IJsonSchema = coalesceSchema(props)(result);
|
|
346
|
+
return {
|
|
347
|
+
name: param.field ?? param.name,
|
|
348
|
+
in: inKeyword,
|
|
349
|
+
schema,
|
|
350
|
+
description: describe(route, "param", param.name) ?? "",
|
|
351
|
+
required: result.success ? result.data.isRequired() : true,
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
function describe(
|
|
356
|
+
route: IRoute,
|
|
357
|
+
tagName: string,
|
|
358
|
+
parameterName?: string,
|
|
359
|
+
): string | undefined {
|
|
360
|
+
const parametric: (elem: ts.JSDocTagInfo) => boolean = parameterName
|
|
361
|
+
? (tag) =>
|
|
362
|
+
tag.text!.find(
|
|
363
|
+
(elem) =>
|
|
364
|
+
elem.kind === "parameterName" &&
|
|
365
|
+
elem.text === parameterName,
|
|
366
|
+
) !== undefined
|
|
367
|
+
: () => true;
|
|
368
|
+
|
|
369
|
+
const tag: ts.JSDocTagInfo | undefined = route.tags.find(
|
|
370
|
+
(tag) => tag.name === tagName && tag.text && parametric(tag),
|
|
371
|
+
);
|
|
372
|
+
return tag && tag.text
|
|
373
|
+
? tag.text.find((elem) => elem.kind === "text")?.text
|
|
374
|
+
: undefined;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const warning = new Singleton((described: boolean) => {
|
|
379
|
+
return new Singleton((type: "request" | "response", method?: string) => {
|
|
380
|
+
const summary =
|
|
381
|
+
type === "request"
|
|
382
|
+
? "Request body must be encrypted."
|
|
383
|
+
: "Response data have been encrypted.";
|
|
384
|
+
const component =
|
|
385
|
+
type === "request"
|
|
386
|
+
? "[EncryptedBody](https://github.com/samchon/@nestia/core#encryptedbody)"
|
|
387
|
+
: `[EncryptedRoute.${method![0].toUpperCase()}.${method!
|
|
388
|
+
.substring(1)
|
|
389
|
+
.toLowerCase()}](https://github.com/samchon/@nestia/core#encryptedroute)`;
|
|
390
|
+
|
|
391
|
+
const content: string[] = [
|
|
392
|
+
"## Warning",
|
|
393
|
+
"",
|
|
394
|
+
summary,
|
|
395
|
+
"",
|
|
396
|
+
`The ${type} body data would be encrypted as "AES-128(256) / CBC mode / PKCS#5 Padding / Base64 Encoding", through the ${component} component.`,
|
|
397
|
+
"",
|
|
398
|
+
`Therefore, just utilize this swagger editor only for referencing. If you need to call the real API, using [SDK](https://github.com/samchon/nestia#software-development-kit) would be much better.`,
|
|
399
|
+
];
|
|
400
|
+
if (described === true) content.push("----------------", "");
|
|
401
|
+
return content.join("\n");
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const any = new Singleton(() =>
|
|
406
|
+
Metadata.from(
|
|
407
|
+
{
|
|
408
|
+
any: true,
|
|
409
|
+
required: true,
|
|
410
|
+
optional: false,
|
|
411
|
+
nullable: false,
|
|
412
|
+
functional: false,
|
|
413
|
+
atomics: [],
|
|
414
|
+
constants: [],
|
|
415
|
+
templates: [],
|
|
416
|
+
escaped: null,
|
|
417
|
+
rest: null,
|
|
418
|
+
arrays: [],
|
|
419
|
+
tuples: [],
|
|
420
|
+
objects: [],
|
|
421
|
+
aliases: [],
|
|
422
|
+
natives: [],
|
|
423
|
+
sets: [],
|
|
424
|
+
maps: [],
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
aliases: [],
|
|
428
|
+
arrays: [],
|
|
429
|
+
tuples: [],
|
|
430
|
+
objects: [],
|
|
431
|
+
},
|
|
432
|
+
),
|
|
433
|
+
);
|