@nestia/sdk 1.3.1 → 1.3.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/assets/config/nestia.config.ts +70 -70
- package/lib/INestiaConfig.d.ts +13 -0
- package/lib/executable/internal/NestiaSdkConfig.js +6 -2
- package/lib/executable/internal/NestiaSdkConfig.js.map +1 -1
- package/lib/executable/sdk.js +11 -11
- package/lib/generates/SwaggerGenerator.js +9 -9
- package/lib/generates/internal/DistributionComposer.js +1 -1
- package/lib/generates/internal/DistributionComposer.js.map +1 -1
- package/lib/generates/internal/E2eFileProgrammer.js +12 -12
- package/lib/generates/internal/SdkFileProgrammer.js +3 -1
- package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkFunctionProgrammer.js +24 -43
- package/lib/generates/internal/SdkFunctionProgrammer.js.map +1 -1
- package/package.json +4 -4
- package/src/INestiaConfig.ts +204 -190
- package/src/NestiaSdkApplication.ts +262 -262
- package/src/analyses/ControllerAnalyzer.ts +261 -261
- package/src/analyses/GenericAnalyzer.ts +53 -53
- package/src/analyses/ImportAnalyzer.ts +164 -164
- package/src/analyses/PathAnalyzer.ts +58 -58
- package/src/analyses/ReflectAnalyzer.ts +321 -321
- package/src/executable/internal/CommandParser.ts +15 -15
- package/src/executable/internal/NestiaConfigCompilerOptions.ts +18 -18
- package/src/executable/internal/NestiaSdkCommand.ts +156 -156
- package/src/executable/internal/NestiaSdkConfig.ts +36 -36
- package/src/executable/internal/nestia.config.getter.ts +12 -12
- package/src/executable/sdk.ts +70 -70
- package/src/generates/E2eGenerator.ts +67 -67
- package/src/generates/SdkGenerator.ts +56 -56
- package/src/generates/SwaggerGenerator.ts +504 -504
- package/src/generates/internal/DistributionComposer.ts +98 -97
- package/src/generates/internal/E2eFileProgrammer.ts +135 -135
- package/src/generates/internal/SdkFileProgrammer.ts +148 -144
- package/src/generates/internal/SdkFunctionProgrammer.ts +30 -52
- package/src/generates/internal/SdkRouteDirectory.ts +21 -21
- package/src/index.ts +4 -4
- package/src/module.ts +2 -2
- package/src/structures/IController.ts +31 -31
- package/src/structures/IRoute.ts +39 -39
- package/src/structures/ISwaggerDocument.ts +120 -120
- package/src/structures/ITypeTuple.ts +6 -6
- package/src/structures/MethodType.ts +11 -11
- package/src/structures/ParamCategory.ts +1 -1
- package/src/structures/TypeEntry.ts +22 -22
- package/src/utils/ArrayUtil.ts +26 -26
- package/src/utils/FileRetriever.ts +22 -22
- package/src/utils/ImportDictionary.ts +56 -56
- package/src/utils/MapUtil.ts +14 -14
- package/src/utils/NestiaConfigUtil.ts +21 -21
- package/src/utils/SourceFinder.ts +60 -60
- package/src/utils/StripEnums.ts +10 -10
|
@@ -1,504 +1,504 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import NodePath from "path";
|
|
3
|
-
import { Singleton } from "tstl/thread/Singleton";
|
|
4
|
-
import { VariadicSingleton } from "tstl/thread/VariadicSingleton";
|
|
5
|
-
import ts from "typescript";
|
|
6
|
-
|
|
7
|
-
import typia from "typia";
|
|
8
|
-
import { IJsonApplication, IJsonSchema } from "typia";
|
|
9
|
-
import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
|
|
10
|
-
import { MetadataFactory } from "typia/lib/factories/MetadataFactory";
|
|
11
|
-
import { Metadata } from "typia/lib/metadata/Metadata";
|
|
12
|
-
import { ApplicationProgrammer } from "typia/lib/programmers/ApplicationProgrammer";
|
|
13
|
-
|
|
14
|
-
import { INestiaConfig } from "../INestiaConfig";
|
|
15
|
-
import { IRoute } from "../structures/IRoute";
|
|
16
|
-
import { ISwaggerDocument } from "../structures/ISwaggerDocument";
|
|
17
|
-
import { FileRetriever } from "../utils/FileRetriever";
|
|
18
|
-
import { MapUtil } from "../utils/MapUtil";
|
|
19
|
-
|
|
20
|
-
export namespace SwaggerGenerator {
|
|
21
|
-
export const generate =
|
|
22
|
-
(checker: ts.TypeChecker) =>
|
|
23
|
-
(config: INestiaConfig.ISwaggerConfig) =>
|
|
24
|
-
async (routeList: IRoute[]): Promise<void> => {
|
|
25
|
-
console.log("Generating Swagger Documents");
|
|
26
|
-
|
|
27
|
-
// PREPARE ASSETS
|
|
28
|
-
const parsed: NodePath.ParsedPath = NodePath.parse(config.output);
|
|
29
|
-
const directory: string = NodePath.dirname(parsed.dir);
|
|
30
|
-
if (fs.existsSync(directory) === false)
|
|
31
|
-
try {
|
|
32
|
-
await fs.promises.mkdir(directory);
|
|
33
|
-
} catch {}
|
|
34
|
-
if (fs.existsSync(directory) === false)
|
|
35
|
-
throw new Error(
|
|
36
|
-
`Error on NestiaApplication.swagger(): failed to create output directory: ${directory}`,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const location: string = !!parsed.ext
|
|
40
|
-
? NodePath.resolve(config.output)
|
|
41
|
-
: NodePath.join(
|
|
42
|
-
NodePath.resolve(config.output),
|
|
43
|
-
"swagger.json",
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const collection: MetadataCollection = new MetadataCollection({
|
|
47
|
-
replace: MetadataCollection.replace,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// CONSTRUCT SWAGGER DOCUMENTS
|
|
51
|
-
const tupleList: Array<ISchemaTuple> = [];
|
|
52
|
-
const swagger: ISwaggerDocument = await initialize(config);
|
|
53
|
-
const pathDict: Map<string, ISwaggerDocument.IPath> = new Map();
|
|
54
|
-
|
|
55
|
-
for (const route of routeList) {
|
|
56
|
-
if (route.tags.find((tag) => tag.name === "internal")) continue;
|
|
57
|
-
|
|
58
|
-
const path: ISwaggerDocument.IPath = MapUtil.take(
|
|
59
|
-
pathDict,
|
|
60
|
-
get_path(route.path, route.parameters),
|
|
61
|
-
() => ({}),
|
|
62
|
-
);
|
|
63
|
-
path[route.method.toLowerCase()] = generate_route(
|
|
64
|
-
checker,
|
|
65
|
-
collection,
|
|
66
|
-
tupleList,
|
|
67
|
-
route,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
swagger.paths = {};
|
|
71
|
-
for (const [path, routes] of pathDict) {
|
|
72
|
-
swagger.paths[path] = routes;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// FILL JSON-SCHEMAS
|
|
76
|
-
const application: IJsonApplication = ApplicationProgrammer.write({
|
|
77
|
-
purpose: "swagger",
|
|
78
|
-
})(tupleList.map(({ metadata }) => metadata));
|
|
79
|
-
swagger.components = {
|
|
80
|
-
...(swagger.components ?? {}),
|
|
81
|
-
...(application.components ?? {}),
|
|
82
|
-
};
|
|
83
|
-
tupleList.forEach(({ schema }, index) => {
|
|
84
|
-
Object.assign(schema, application.schemas[index]!);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// CONFIGURE SECURITY
|
|
88
|
-
if (config.security) fill_security(config.security, swagger);
|
|
89
|
-
|
|
90
|
-
// DO GENERATE
|
|
91
|
-
await fs.promises.writeFile(
|
|
92
|
-
location,
|
|
93
|
-
JSON.stringify(swagger, null, 2),
|
|
94
|
-
"utf8",
|
|
95
|
-
);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/* ---------------------------------------------------------
|
|
99
|
-
INITIALIZERS
|
|
100
|
-
--------------------------------------------------------- */
|
|
101
|
-
const initialize = async (
|
|
102
|
-
config: INestiaConfig.ISwaggerConfig,
|
|
103
|
-
): Promise<ISwaggerDocument> => {
|
|
104
|
-
const pack = new Singleton(
|
|
105
|
-
async (): Promise<Partial<ISwaggerDocument.IInfo> | null> => {
|
|
106
|
-
const location: string | null = await FileRetriever.file(
|
|
107
|
-
"package.json",
|
|
108
|
-
)(process.cwd());
|
|
109
|
-
if (location === null) return null;
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
const content: string = await fs.promises.readFile(
|
|
113
|
-
location,
|
|
114
|
-
"utf8",
|
|
115
|
-
);
|
|
116
|
-
const data = typia.assertParse<{
|
|
117
|
-
name?: string;
|
|
118
|
-
version?: string;
|
|
119
|
-
description?: string;
|
|
120
|
-
}>(content);
|
|
121
|
-
return {
|
|
122
|
-
title: data.name,
|
|
123
|
-
version: data.version,
|
|
124
|
-
description: data.description,
|
|
125
|
-
};
|
|
126
|
-
} catch {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
openapi: "3.0.1",
|
|
134
|
-
servers: config.servers ?? [
|
|
135
|
-
{
|
|
136
|
-
url: "https://github.com/samchon/nestia",
|
|
137
|
-
description: "insert your server url",
|
|
138
|
-
},
|
|
139
|
-
],
|
|
140
|
-
info: {
|
|
141
|
-
version:
|
|
142
|
-
config.info?.version ??
|
|
143
|
-
(await pack.get())?.version ??
|
|
144
|
-
"0.1.0",
|
|
145
|
-
title:
|
|
146
|
-
config.info?.title ??
|
|
147
|
-
(await pack.get())?.title ??
|
|
148
|
-
"Swagger Documents",
|
|
149
|
-
description:
|
|
150
|
-
config.info?.description ??
|
|
151
|
-
(await pack.get())?.description ??
|
|
152
|
-
"Generated by nestia - https://github.com/samchon/nestia",
|
|
153
|
-
},
|
|
154
|
-
paths: {},
|
|
155
|
-
components: {},
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
function get_path(path: string, parameters: IRoute.IParameter[]): string {
|
|
160
|
-
const filtered: IRoute.IParameter[] = parameters.filter(
|
|
161
|
-
(param) => param.category === "param" && !!param.field,
|
|
162
|
-
);
|
|
163
|
-
for (const param of filtered)
|
|
164
|
-
path = path.replace(`:${param.field}`, `{${param.field}}`);
|
|
165
|
-
return path;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function generate_route(
|
|
169
|
-
checker: ts.TypeChecker,
|
|
170
|
-
collection: MetadataCollection,
|
|
171
|
-
tupleList: Array<ISchemaTuple>,
|
|
172
|
-
route: IRoute,
|
|
173
|
-
): ISwaggerDocument.IRoute {
|
|
174
|
-
const bodyParam = route.parameters.find(
|
|
175
|
-
(param) => param.category === "body",
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
const getTagTexts = (name: string) =>
|
|
179
|
-
route.tags
|
|
180
|
-
.filter(
|
|
181
|
-
(tag) =>
|
|
182
|
-
tag.name === name &&
|
|
183
|
-
tag.text &&
|
|
184
|
-
tag.text.find(
|
|
185
|
-
(elem) => elem.kind === "text" && elem.text.length,
|
|
186
|
-
) !== undefined,
|
|
187
|
-
)
|
|
188
|
-
.map(
|
|
189
|
-
(tag) =>
|
|
190
|
-
tag.text!.find((elem) => elem.kind === "text")!.text,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const description: string | undefined = route.description?.length
|
|
194
|
-
? route.description
|
|
195
|
-
: undefined;
|
|
196
|
-
const summary: string | undefined = (() => {
|
|
197
|
-
if (description === undefined) return undefined;
|
|
198
|
-
|
|
199
|
-
const [explicit] = getTagTexts("summary");
|
|
200
|
-
if (explicit?.length) return explicit;
|
|
201
|
-
|
|
202
|
-
const index: number = description.indexOf(".");
|
|
203
|
-
if (index <= 0) return undefined;
|
|
204
|
-
|
|
205
|
-
const content: string = description.substring(0, index).trim();
|
|
206
|
-
return content.length ? content : undefined;
|
|
207
|
-
})();
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
tags: getTagTexts("tag"),
|
|
211
|
-
parameters: route.parameters
|
|
212
|
-
.filter((param) => param.category !== "body")
|
|
213
|
-
.map((param) =>
|
|
214
|
-
generate_parameter(
|
|
215
|
-
checker,
|
|
216
|
-
collection,
|
|
217
|
-
tupleList,
|
|
218
|
-
route,
|
|
219
|
-
param,
|
|
220
|
-
),
|
|
221
|
-
),
|
|
222
|
-
requestBody: bodyParam
|
|
223
|
-
? generate_request_body(
|
|
224
|
-
checker,
|
|
225
|
-
collection,
|
|
226
|
-
tupleList,
|
|
227
|
-
route,
|
|
228
|
-
bodyParam,
|
|
229
|
-
)
|
|
230
|
-
: undefined,
|
|
231
|
-
responses: generate_response_body(
|
|
232
|
-
checker,
|
|
233
|
-
collection,
|
|
234
|
-
tupleList,
|
|
235
|
-
route,
|
|
236
|
-
),
|
|
237
|
-
summary,
|
|
238
|
-
description,
|
|
239
|
-
"x-nestia-namespace": [
|
|
240
|
-
...route.path
|
|
241
|
-
.split("/")
|
|
242
|
-
.filter((str) => str.length && str[0] !== ":"),
|
|
243
|
-
route.name,
|
|
244
|
-
].join("."),
|
|
245
|
-
"x-nestia-jsDocTags": route.tags,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function fill_security(
|
|
250
|
-
security: Required<INestiaConfig.ISwaggerConfig>["security"],
|
|
251
|
-
swagger: ISwaggerDocument,
|
|
252
|
-
): void {
|
|
253
|
-
swagger.security = [{}];
|
|
254
|
-
swagger.components.securitySchemes = {};
|
|
255
|
-
|
|
256
|
-
for (const [key, value] of Object.entries(security)) {
|
|
257
|
-
swagger.security[0]![key] = [];
|
|
258
|
-
swagger.components.securitySchemes[key] = emend_security(value);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function emend_security(
|
|
263
|
-
input: INestiaConfig.ISwaggerConfig.ISecurityScheme,
|
|
264
|
-
): ISwaggerDocument.ISecurityScheme {
|
|
265
|
-
if (input.type === "apiKey")
|
|
266
|
-
return {
|
|
267
|
-
...input,
|
|
268
|
-
in: input.in ?? "header",
|
|
269
|
-
name: input.name ?? "Authorization",
|
|
270
|
-
};
|
|
271
|
-
return input;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/* ---------------------------------------------------------
|
|
275
|
-
REQUEST & RESPONSE
|
|
276
|
-
--------------------------------------------------------- */
|
|
277
|
-
function generate_parameter(
|
|
278
|
-
checker: ts.TypeChecker,
|
|
279
|
-
collection: MetadataCollection,
|
|
280
|
-
tupleList: Array<ISchemaTuple>,
|
|
281
|
-
route: IRoute,
|
|
282
|
-
parameter: IRoute.IParameter,
|
|
283
|
-
): ISwaggerDocument.IParameter {
|
|
284
|
-
const schema: IJsonSchema | null = generate_schema(
|
|
285
|
-
checker,
|
|
286
|
-
collection,
|
|
287
|
-
tupleList,
|
|
288
|
-
parameter.type.type,
|
|
289
|
-
);
|
|
290
|
-
if (schema === null)
|
|
291
|
-
throw new Error(
|
|
292
|
-
`Error on NestiaApplication.swagger(): invalid parameter type on ${route.symbol}#${parameter.name}`,
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
name: parameter.field ?? parameter.name,
|
|
297
|
-
in: parameter.category === "param" ? "path" : parameter.category,
|
|
298
|
-
description:
|
|
299
|
-
get_parametric_description(route, "param", parameter.name) ||
|
|
300
|
-
"",
|
|
301
|
-
schema,
|
|
302
|
-
required: required(parameter.type.type),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function generate_request_body(
|
|
307
|
-
checker: ts.TypeChecker,
|
|
308
|
-
collection: MetadataCollection,
|
|
309
|
-
tupleList: Array<ISchemaTuple>,
|
|
310
|
-
route: IRoute,
|
|
311
|
-
parameter: IRoute.IParameter,
|
|
312
|
-
): ISwaggerDocument.IRequestBody {
|
|
313
|
-
const schema: IJsonSchema | null = generate_schema(
|
|
314
|
-
checker,
|
|
315
|
-
collection,
|
|
316
|
-
tupleList,
|
|
317
|
-
parameter.type.type,
|
|
318
|
-
);
|
|
319
|
-
if (schema === null)
|
|
320
|
-
throw new Error(
|
|
321
|
-
`Error on NestiaApplication.sdk(): invalid request body type on ${route.symbol}.`,
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
description:
|
|
326
|
-
warning.get(parameter.encrypted).get("request") +
|
|
327
|
-
(get_parametric_description(route, "param", parameter.name) ??
|
|
328
|
-
""),
|
|
329
|
-
content: {
|
|
330
|
-
"application/json": {
|
|
331
|
-
schema,
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
required: true,
|
|
335
|
-
"x-nestia-encrypted": parameter.encrypted,
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function generate_response_body(
|
|
340
|
-
checker: ts.TypeChecker,
|
|
341
|
-
collection: MetadataCollection,
|
|
342
|
-
tupleList: Array<ISchemaTuple>,
|
|
343
|
-
route: IRoute,
|
|
344
|
-
): ISwaggerDocument.IResponseBody {
|
|
345
|
-
// OUTPUT WITH SUCCESS STATUS
|
|
346
|
-
const status: string =
|
|
347
|
-
route.status !== undefined
|
|
348
|
-
? String(route.status)
|
|
349
|
-
: route.method === "GET" || route.method === "DELETE"
|
|
350
|
-
? "200"
|
|
351
|
-
: "201";
|
|
352
|
-
const schema: IJsonSchema | null = generate_schema(
|
|
353
|
-
checker,
|
|
354
|
-
collection,
|
|
355
|
-
tupleList,
|
|
356
|
-
route.output.type,
|
|
357
|
-
);
|
|
358
|
-
const success: ISwaggerDocument.IResponseBody = {
|
|
359
|
-
[status]: {
|
|
360
|
-
description:
|
|
361
|
-
warning.get(route.encrypted).get("response", route.method) +
|
|
362
|
-
(get_parametric_description(route, "return") ??
|
|
363
|
-
get_parametric_description(route, "returns") ??
|
|
364
|
-
""),
|
|
365
|
-
content:
|
|
366
|
-
schema === null || route.output.name === "void"
|
|
367
|
-
? undefined
|
|
368
|
-
: {
|
|
369
|
-
"application/json": {
|
|
370
|
-
schema,
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
"x-nestia-encrypted": route.encrypted,
|
|
374
|
-
},
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
// EXCEPTION STATUSES
|
|
378
|
-
const exceptions: ISwaggerDocument.IResponseBody = Object.fromEntries(
|
|
379
|
-
route.tags
|
|
380
|
-
.filter(
|
|
381
|
-
(tag) =>
|
|
382
|
-
(tag.name === "throw" || tag.name === "throws") &&
|
|
383
|
-
tag.text &&
|
|
384
|
-
tag.text.find(
|
|
385
|
-
(elem) =>
|
|
386
|
-
elem.kind === "text" &&
|
|
387
|
-
isNaN(
|
|
388
|
-
Number(
|
|
389
|
-
elem.text
|
|
390
|
-
.split(" ")
|
|
391
|
-
.map((str) => str.trim())[0],
|
|
392
|
-
),
|
|
393
|
-
) === false,
|
|
394
|
-
) !== undefined,
|
|
395
|
-
)
|
|
396
|
-
.map((tag) => {
|
|
397
|
-
const text: string = tag.text!.find(
|
|
398
|
-
(elem) => elem.kind === "text",
|
|
399
|
-
)!.text;
|
|
400
|
-
const elements: string[] = text
|
|
401
|
-
.split(" ")
|
|
402
|
-
.map((str) => str.trim());
|
|
403
|
-
|
|
404
|
-
return [
|
|
405
|
-
elements[0],
|
|
406
|
-
{
|
|
407
|
-
description: elements.slice(1).join(" "),
|
|
408
|
-
},
|
|
409
|
-
];
|
|
410
|
-
}),
|
|
411
|
-
);
|
|
412
|
-
return { ...exceptions, ...success };
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/* ---------------------------------------------------------
|
|
416
|
-
UTILS
|
|
417
|
-
--------------------------------------------------------- */
|
|
418
|
-
function generate_schema(
|
|
419
|
-
checker: ts.TypeChecker,
|
|
420
|
-
collection: MetadataCollection,
|
|
421
|
-
tupleList: Array<ISchemaTuple>,
|
|
422
|
-
type: ts.Type,
|
|
423
|
-
): IJsonSchema | null {
|
|
424
|
-
const metadata: Metadata = MetadataFactory.analyze(checker)({
|
|
425
|
-
resolve: false,
|
|
426
|
-
constant: true,
|
|
427
|
-
absorb: false,
|
|
428
|
-
})(collection)(type);
|
|
429
|
-
if (metadata.empty() && metadata.nullable === false) return null;
|
|
430
|
-
|
|
431
|
-
const schema: IJsonSchema = {} as IJsonSchema;
|
|
432
|
-
tupleList.push({ metadata, schema });
|
|
433
|
-
return schema;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function get_parametric_description(
|
|
437
|
-
route: IRoute,
|
|
438
|
-
tagName: string,
|
|
439
|
-
parameterName?: string,
|
|
440
|
-
): string | undefined {
|
|
441
|
-
const parametric: (elem: ts.JSDocTagInfo) => boolean = parameterName
|
|
442
|
-
? (tag) =>
|
|
443
|
-
tag.text!.find(
|
|
444
|
-
(elem) =>
|
|
445
|
-
elem.kind === "parameterName" &&
|
|
446
|
-
elem.text === parameterName,
|
|
447
|
-
) !== undefined
|
|
448
|
-
: () => true;
|
|
449
|
-
|
|
450
|
-
const tag: ts.JSDocTagInfo | undefined = route.tags.find(
|
|
451
|
-
(tag) => tag.name === tagName && tag.text && parametric(tag),
|
|
452
|
-
);
|
|
453
|
-
return tag && tag.text
|
|
454
|
-
? tag.text.find((elem) => elem.kind === "text")?.text
|
|
455
|
-
: undefined;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const required = (type: ts.Type): boolean => {
|
|
460
|
-
if (type.isUnion()) return type.types.every((type) => required(type));
|
|
461
|
-
const obstacle = (other: ts.TypeFlags) => (type.getFlags() & other) === 0;
|
|
462
|
-
return (
|
|
463
|
-
obstacle(ts.TypeFlags.Undefined) &&
|
|
464
|
-
obstacle(ts.TypeFlags.Never) &&
|
|
465
|
-
obstacle(ts.TypeFlags.Void) &&
|
|
466
|
-
obstacle(ts.TypeFlags.VoidLike)
|
|
467
|
-
);
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const warning = new VariadicSingleton((encrypted: boolean) => {
|
|
471
|
-
if (encrypted === false) return new Singleton(() => "");
|
|
472
|
-
|
|
473
|
-
return new VariadicSingleton(
|
|
474
|
-
(type: "request" | "response", method?: string) => {
|
|
475
|
-
const summary =
|
|
476
|
-
type === "request"
|
|
477
|
-
? "Request body must be encrypted."
|
|
478
|
-
: "Response data have been encrypted.";
|
|
479
|
-
|
|
480
|
-
const component =
|
|
481
|
-
type === "request"
|
|
482
|
-
? "[EncryptedBody](https://github.com/samchon/@nestia/core#encryptedbody)"
|
|
483
|
-
: `[EncryptedRoute.${method![0].toUpperCase()}.${method!
|
|
484
|
-
.substring(1)
|
|
485
|
-
.toLowerCase()}](https://github.com/samchon/@nestia/core#encryptedroute)`;
|
|
486
|
-
|
|
487
|
-
return `## Warning
|
|
488
|
-
${summary}
|
|
489
|
-
|
|
490
|
-
The ${type} body data would be encrypted as "AES-128(256) / CBC mode / PKCS#5 Padding / Base64 Encoding", through the ${component} component.
|
|
491
|
-
|
|
492
|
-
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.
|
|
493
|
-
|
|
494
|
-
-----------------
|
|
495
|
-
|
|
496
|
-
`;
|
|
497
|
-
},
|
|
498
|
-
);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
interface ISchemaTuple {
|
|
502
|
-
metadata: Metadata;
|
|
503
|
-
schema: IJsonSchema;
|
|
504
|
-
}
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import NodePath from "path";
|
|
3
|
+
import { Singleton } from "tstl/thread/Singleton";
|
|
4
|
+
import { VariadicSingleton } from "tstl/thread/VariadicSingleton";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
|
|
7
|
+
import typia from "typia";
|
|
8
|
+
import { IJsonApplication, IJsonSchema } from "typia";
|
|
9
|
+
import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
|
|
10
|
+
import { MetadataFactory } from "typia/lib/factories/MetadataFactory";
|
|
11
|
+
import { Metadata } from "typia/lib/metadata/Metadata";
|
|
12
|
+
import { ApplicationProgrammer } from "typia/lib/programmers/ApplicationProgrammer";
|
|
13
|
+
|
|
14
|
+
import { INestiaConfig } from "../INestiaConfig";
|
|
15
|
+
import { IRoute } from "../structures/IRoute";
|
|
16
|
+
import { ISwaggerDocument } from "../structures/ISwaggerDocument";
|
|
17
|
+
import { FileRetriever } from "../utils/FileRetriever";
|
|
18
|
+
import { MapUtil } from "../utils/MapUtil";
|
|
19
|
+
|
|
20
|
+
export namespace SwaggerGenerator {
|
|
21
|
+
export const generate =
|
|
22
|
+
(checker: ts.TypeChecker) =>
|
|
23
|
+
(config: INestiaConfig.ISwaggerConfig) =>
|
|
24
|
+
async (routeList: IRoute[]): Promise<void> => {
|
|
25
|
+
console.log("Generating Swagger Documents");
|
|
26
|
+
|
|
27
|
+
// PREPARE ASSETS
|
|
28
|
+
const parsed: NodePath.ParsedPath = NodePath.parse(config.output);
|
|
29
|
+
const directory: string = NodePath.dirname(parsed.dir);
|
|
30
|
+
if (fs.existsSync(directory) === false)
|
|
31
|
+
try {
|
|
32
|
+
await fs.promises.mkdir(directory);
|
|
33
|
+
} catch {}
|
|
34
|
+
if (fs.existsSync(directory) === false)
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Error on NestiaApplication.swagger(): failed to create output directory: ${directory}`,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const location: string = !!parsed.ext
|
|
40
|
+
? NodePath.resolve(config.output)
|
|
41
|
+
: NodePath.join(
|
|
42
|
+
NodePath.resolve(config.output),
|
|
43
|
+
"swagger.json",
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const collection: MetadataCollection = new MetadataCollection({
|
|
47
|
+
replace: MetadataCollection.replace,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// CONSTRUCT SWAGGER DOCUMENTS
|
|
51
|
+
const tupleList: Array<ISchemaTuple> = [];
|
|
52
|
+
const swagger: ISwaggerDocument = await initialize(config);
|
|
53
|
+
const pathDict: Map<string, ISwaggerDocument.IPath> = new Map();
|
|
54
|
+
|
|
55
|
+
for (const route of routeList) {
|
|
56
|
+
if (route.tags.find((tag) => tag.name === "internal")) continue;
|
|
57
|
+
|
|
58
|
+
const path: ISwaggerDocument.IPath = MapUtil.take(
|
|
59
|
+
pathDict,
|
|
60
|
+
get_path(route.path, route.parameters),
|
|
61
|
+
() => ({}),
|
|
62
|
+
);
|
|
63
|
+
path[route.method.toLowerCase()] = generate_route(
|
|
64
|
+
checker,
|
|
65
|
+
collection,
|
|
66
|
+
tupleList,
|
|
67
|
+
route,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
swagger.paths = {};
|
|
71
|
+
for (const [path, routes] of pathDict) {
|
|
72
|
+
swagger.paths[path] = routes;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// FILL JSON-SCHEMAS
|
|
76
|
+
const application: IJsonApplication = ApplicationProgrammer.write({
|
|
77
|
+
purpose: "swagger",
|
|
78
|
+
})(tupleList.map(({ metadata }) => metadata));
|
|
79
|
+
swagger.components = {
|
|
80
|
+
...(swagger.components ?? {}),
|
|
81
|
+
...(application.components ?? {}),
|
|
82
|
+
};
|
|
83
|
+
tupleList.forEach(({ schema }, index) => {
|
|
84
|
+
Object.assign(schema, application.schemas[index]!);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// CONFIGURE SECURITY
|
|
88
|
+
if (config.security) fill_security(config.security, swagger);
|
|
89
|
+
|
|
90
|
+
// DO GENERATE
|
|
91
|
+
await fs.promises.writeFile(
|
|
92
|
+
location,
|
|
93
|
+
JSON.stringify(swagger, null, 2),
|
|
94
|
+
"utf8",
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/* ---------------------------------------------------------
|
|
99
|
+
INITIALIZERS
|
|
100
|
+
--------------------------------------------------------- */
|
|
101
|
+
const initialize = async (
|
|
102
|
+
config: INestiaConfig.ISwaggerConfig,
|
|
103
|
+
): Promise<ISwaggerDocument> => {
|
|
104
|
+
const pack = new Singleton(
|
|
105
|
+
async (): Promise<Partial<ISwaggerDocument.IInfo> | null> => {
|
|
106
|
+
const location: string | null = await FileRetriever.file(
|
|
107
|
+
"package.json",
|
|
108
|
+
)(process.cwd());
|
|
109
|
+
if (location === null) return null;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const content: string = await fs.promises.readFile(
|
|
113
|
+
location,
|
|
114
|
+
"utf8",
|
|
115
|
+
);
|
|
116
|
+
const data = typia.assertParse<{
|
|
117
|
+
name?: string;
|
|
118
|
+
version?: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
}>(content);
|
|
121
|
+
return {
|
|
122
|
+
title: data.name,
|
|
123
|
+
version: data.version,
|
|
124
|
+
description: data.description,
|
|
125
|
+
};
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
openapi: "3.0.1",
|
|
134
|
+
servers: config.servers ?? [
|
|
135
|
+
{
|
|
136
|
+
url: "https://github.com/samchon/nestia",
|
|
137
|
+
description: "insert your server url",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
info: {
|
|
141
|
+
version:
|
|
142
|
+
config.info?.version ??
|
|
143
|
+
(await pack.get())?.version ??
|
|
144
|
+
"0.1.0",
|
|
145
|
+
title:
|
|
146
|
+
config.info?.title ??
|
|
147
|
+
(await pack.get())?.title ??
|
|
148
|
+
"Swagger Documents",
|
|
149
|
+
description:
|
|
150
|
+
config.info?.description ??
|
|
151
|
+
(await pack.get())?.description ??
|
|
152
|
+
"Generated by nestia - https://github.com/samchon/nestia",
|
|
153
|
+
},
|
|
154
|
+
paths: {},
|
|
155
|
+
components: {},
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function get_path(path: string, parameters: IRoute.IParameter[]): string {
|
|
160
|
+
const filtered: IRoute.IParameter[] = parameters.filter(
|
|
161
|
+
(param) => param.category === "param" && !!param.field,
|
|
162
|
+
);
|
|
163
|
+
for (const param of filtered)
|
|
164
|
+
path = path.replace(`:${param.field}`, `{${param.field}}`);
|
|
165
|
+
return path;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function generate_route(
|
|
169
|
+
checker: ts.TypeChecker,
|
|
170
|
+
collection: MetadataCollection,
|
|
171
|
+
tupleList: Array<ISchemaTuple>,
|
|
172
|
+
route: IRoute,
|
|
173
|
+
): ISwaggerDocument.IRoute {
|
|
174
|
+
const bodyParam = route.parameters.find(
|
|
175
|
+
(param) => param.category === "body",
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const getTagTexts = (name: string) =>
|
|
179
|
+
route.tags
|
|
180
|
+
.filter(
|
|
181
|
+
(tag) =>
|
|
182
|
+
tag.name === name &&
|
|
183
|
+
tag.text &&
|
|
184
|
+
tag.text.find(
|
|
185
|
+
(elem) => elem.kind === "text" && elem.text.length,
|
|
186
|
+
) !== undefined,
|
|
187
|
+
)
|
|
188
|
+
.map(
|
|
189
|
+
(tag) =>
|
|
190
|
+
tag.text!.find((elem) => elem.kind === "text")!.text,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const description: string | undefined = route.description?.length
|
|
194
|
+
? route.description
|
|
195
|
+
: undefined;
|
|
196
|
+
const summary: string | undefined = (() => {
|
|
197
|
+
if (description === undefined) return undefined;
|
|
198
|
+
|
|
199
|
+
const [explicit] = getTagTexts("summary");
|
|
200
|
+
if (explicit?.length) return explicit;
|
|
201
|
+
|
|
202
|
+
const index: number = description.indexOf(".");
|
|
203
|
+
if (index <= 0) return undefined;
|
|
204
|
+
|
|
205
|
+
const content: string = description.substring(0, index).trim();
|
|
206
|
+
return content.length ? content : undefined;
|
|
207
|
+
})();
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
tags: getTagTexts("tag"),
|
|
211
|
+
parameters: route.parameters
|
|
212
|
+
.filter((param) => param.category !== "body")
|
|
213
|
+
.map((param) =>
|
|
214
|
+
generate_parameter(
|
|
215
|
+
checker,
|
|
216
|
+
collection,
|
|
217
|
+
tupleList,
|
|
218
|
+
route,
|
|
219
|
+
param,
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
requestBody: bodyParam
|
|
223
|
+
? generate_request_body(
|
|
224
|
+
checker,
|
|
225
|
+
collection,
|
|
226
|
+
tupleList,
|
|
227
|
+
route,
|
|
228
|
+
bodyParam,
|
|
229
|
+
)
|
|
230
|
+
: undefined,
|
|
231
|
+
responses: generate_response_body(
|
|
232
|
+
checker,
|
|
233
|
+
collection,
|
|
234
|
+
tupleList,
|
|
235
|
+
route,
|
|
236
|
+
),
|
|
237
|
+
summary,
|
|
238
|
+
description,
|
|
239
|
+
"x-nestia-namespace": [
|
|
240
|
+
...route.path
|
|
241
|
+
.split("/")
|
|
242
|
+
.filter((str) => str.length && str[0] !== ":"),
|
|
243
|
+
route.name,
|
|
244
|
+
].join("."),
|
|
245
|
+
"x-nestia-jsDocTags": route.tags,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function fill_security(
|
|
250
|
+
security: Required<INestiaConfig.ISwaggerConfig>["security"],
|
|
251
|
+
swagger: ISwaggerDocument,
|
|
252
|
+
): void {
|
|
253
|
+
swagger.security = [{}];
|
|
254
|
+
swagger.components.securitySchemes = {};
|
|
255
|
+
|
|
256
|
+
for (const [key, value] of Object.entries(security)) {
|
|
257
|
+
swagger.security[0]![key] = [];
|
|
258
|
+
swagger.components.securitySchemes[key] = emend_security(value);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function emend_security(
|
|
263
|
+
input: INestiaConfig.ISwaggerConfig.ISecurityScheme,
|
|
264
|
+
): ISwaggerDocument.ISecurityScheme {
|
|
265
|
+
if (input.type === "apiKey")
|
|
266
|
+
return {
|
|
267
|
+
...input,
|
|
268
|
+
in: input.in ?? "header",
|
|
269
|
+
name: input.name ?? "Authorization",
|
|
270
|
+
};
|
|
271
|
+
return input;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* ---------------------------------------------------------
|
|
275
|
+
REQUEST & RESPONSE
|
|
276
|
+
--------------------------------------------------------- */
|
|
277
|
+
function generate_parameter(
|
|
278
|
+
checker: ts.TypeChecker,
|
|
279
|
+
collection: MetadataCollection,
|
|
280
|
+
tupleList: Array<ISchemaTuple>,
|
|
281
|
+
route: IRoute,
|
|
282
|
+
parameter: IRoute.IParameter,
|
|
283
|
+
): ISwaggerDocument.IParameter {
|
|
284
|
+
const schema: IJsonSchema | null = generate_schema(
|
|
285
|
+
checker,
|
|
286
|
+
collection,
|
|
287
|
+
tupleList,
|
|
288
|
+
parameter.type.type,
|
|
289
|
+
);
|
|
290
|
+
if (schema === null)
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Error on NestiaApplication.swagger(): invalid parameter type on ${route.symbol}#${parameter.name}`,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
name: parameter.field ?? parameter.name,
|
|
297
|
+
in: parameter.category === "param" ? "path" : parameter.category,
|
|
298
|
+
description:
|
|
299
|
+
get_parametric_description(route, "param", parameter.name) ||
|
|
300
|
+
"",
|
|
301
|
+
schema,
|
|
302
|
+
required: required(parameter.type.type),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function generate_request_body(
|
|
307
|
+
checker: ts.TypeChecker,
|
|
308
|
+
collection: MetadataCollection,
|
|
309
|
+
tupleList: Array<ISchemaTuple>,
|
|
310
|
+
route: IRoute,
|
|
311
|
+
parameter: IRoute.IParameter,
|
|
312
|
+
): ISwaggerDocument.IRequestBody {
|
|
313
|
+
const schema: IJsonSchema | null = generate_schema(
|
|
314
|
+
checker,
|
|
315
|
+
collection,
|
|
316
|
+
tupleList,
|
|
317
|
+
parameter.type.type,
|
|
318
|
+
);
|
|
319
|
+
if (schema === null)
|
|
320
|
+
throw new Error(
|
|
321
|
+
`Error on NestiaApplication.sdk(): invalid request body type on ${route.symbol}.`,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
description:
|
|
326
|
+
warning.get(parameter.encrypted).get("request") +
|
|
327
|
+
(get_parametric_description(route, "param", parameter.name) ??
|
|
328
|
+
""),
|
|
329
|
+
content: {
|
|
330
|
+
"application/json": {
|
|
331
|
+
schema,
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
required: true,
|
|
335
|
+
"x-nestia-encrypted": parameter.encrypted,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function generate_response_body(
|
|
340
|
+
checker: ts.TypeChecker,
|
|
341
|
+
collection: MetadataCollection,
|
|
342
|
+
tupleList: Array<ISchemaTuple>,
|
|
343
|
+
route: IRoute,
|
|
344
|
+
): ISwaggerDocument.IResponseBody {
|
|
345
|
+
// OUTPUT WITH SUCCESS STATUS
|
|
346
|
+
const status: string =
|
|
347
|
+
route.status !== undefined
|
|
348
|
+
? String(route.status)
|
|
349
|
+
: route.method === "GET" || route.method === "DELETE"
|
|
350
|
+
? "200"
|
|
351
|
+
: "201";
|
|
352
|
+
const schema: IJsonSchema | null = generate_schema(
|
|
353
|
+
checker,
|
|
354
|
+
collection,
|
|
355
|
+
tupleList,
|
|
356
|
+
route.output.type,
|
|
357
|
+
);
|
|
358
|
+
const success: ISwaggerDocument.IResponseBody = {
|
|
359
|
+
[status]: {
|
|
360
|
+
description:
|
|
361
|
+
warning.get(route.encrypted).get("response", route.method) +
|
|
362
|
+
(get_parametric_description(route, "return") ??
|
|
363
|
+
get_parametric_description(route, "returns") ??
|
|
364
|
+
""),
|
|
365
|
+
content:
|
|
366
|
+
schema === null || route.output.name === "void"
|
|
367
|
+
? undefined
|
|
368
|
+
: {
|
|
369
|
+
"application/json": {
|
|
370
|
+
schema,
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
"x-nestia-encrypted": route.encrypted,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// EXCEPTION STATUSES
|
|
378
|
+
const exceptions: ISwaggerDocument.IResponseBody = Object.fromEntries(
|
|
379
|
+
route.tags
|
|
380
|
+
.filter(
|
|
381
|
+
(tag) =>
|
|
382
|
+
(tag.name === "throw" || tag.name === "throws") &&
|
|
383
|
+
tag.text &&
|
|
384
|
+
tag.text.find(
|
|
385
|
+
(elem) =>
|
|
386
|
+
elem.kind === "text" &&
|
|
387
|
+
isNaN(
|
|
388
|
+
Number(
|
|
389
|
+
elem.text
|
|
390
|
+
.split(" ")
|
|
391
|
+
.map((str) => str.trim())[0],
|
|
392
|
+
),
|
|
393
|
+
) === false,
|
|
394
|
+
) !== undefined,
|
|
395
|
+
)
|
|
396
|
+
.map((tag) => {
|
|
397
|
+
const text: string = tag.text!.find(
|
|
398
|
+
(elem) => elem.kind === "text",
|
|
399
|
+
)!.text;
|
|
400
|
+
const elements: string[] = text
|
|
401
|
+
.split(" ")
|
|
402
|
+
.map((str) => str.trim());
|
|
403
|
+
|
|
404
|
+
return [
|
|
405
|
+
elements[0],
|
|
406
|
+
{
|
|
407
|
+
description: elements.slice(1).join(" "),
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
return { ...exceptions, ...success };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* ---------------------------------------------------------
|
|
416
|
+
UTILS
|
|
417
|
+
--------------------------------------------------------- */
|
|
418
|
+
function generate_schema(
|
|
419
|
+
checker: ts.TypeChecker,
|
|
420
|
+
collection: MetadataCollection,
|
|
421
|
+
tupleList: Array<ISchemaTuple>,
|
|
422
|
+
type: ts.Type,
|
|
423
|
+
): IJsonSchema | null {
|
|
424
|
+
const metadata: Metadata = MetadataFactory.analyze(checker)({
|
|
425
|
+
resolve: false,
|
|
426
|
+
constant: true,
|
|
427
|
+
absorb: false,
|
|
428
|
+
})(collection)(type);
|
|
429
|
+
if (metadata.empty() && metadata.nullable === false) return null;
|
|
430
|
+
|
|
431
|
+
const schema: IJsonSchema = {} as IJsonSchema;
|
|
432
|
+
tupleList.push({ metadata, schema });
|
|
433
|
+
return schema;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function get_parametric_description(
|
|
437
|
+
route: IRoute,
|
|
438
|
+
tagName: string,
|
|
439
|
+
parameterName?: string,
|
|
440
|
+
): string | undefined {
|
|
441
|
+
const parametric: (elem: ts.JSDocTagInfo) => boolean = parameterName
|
|
442
|
+
? (tag) =>
|
|
443
|
+
tag.text!.find(
|
|
444
|
+
(elem) =>
|
|
445
|
+
elem.kind === "parameterName" &&
|
|
446
|
+
elem.text === parameterName,
|
|
447
|
+
) !== undefined
|
|
448
|
+
: () => true;
|
|
449
|
+
|
|
450
|
+
const tag: ts.JSDocTagInfo | undefined = route.tags.find(
|
|
451
|
+
(tag) => tag.name === tagName && tag.text && parametric(tag),
|
|
452
|
+
);
|
|
453
|
+
return tag && tag.text
|
|
454
|
+
? tag.text.find((elem) => elem.kind === "text")?.text
|
|
455
|
+
: undefined;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const required = (type: ts.Type): boolean => {
|
|
460
|
+
if (type.isUnion()) return type.types.every((type) => required(type));
|
|
461
|
+
const obstacle = (other: ts.TypeFlags) => (type.getFlags() & other) === 0;
|
|
462
|
+
return (
|
|
463
|
+
obstacle(ts.TypeFlags.Undefined) &&
|
|
464
|
+
obstacle(ts.TypeFlags.Never) &&
|
|
465
|
+
obstacle(ts.TypeFlags.Void) &&
|
|
466
|
+
obstacle(ts.TypeFlags.VoidLike)
|
|
467
|
+
);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const warning = new VariadicSingleton((encrypted: boolean) => {
|
|
471
|
+
if (encrypted === false) return new Singleton(() => "");
|
|
472
|
+
|
|
473
|
+
return new VariadicSingleton(
|
|
474
|
+
(type: "request" | "response", method?: string) => {
|
|
475
|
+
const summary =
|
|
476
|
+
type === "request"
|
|
477
|
+
? "Request body must be encrypted."
|
|
478
|
+
: "Response data have been encrypted.";
|
|
479
|
+
|
|
480
|
+
const component =
|
|
481
|
+
type === "request"
|
|
482
|
+
? "[EncryptedBody](https://github.com/samchon/@nestia/core#encryptedbody)"
|
|
483
|
+
: `[EncryptedRoute.${method![0].toUpperCase()}.${method!
|
|
484
|
+
.substring(1)
|
|
485
|
+
.toLowerCase()}](https://github.com/samchon/@nestia/core#encryptedroute)`;
|
|
486
|
+
|
|
487
|
+
return `## Warning
|
|
488
|
+
${summary}
|
|
489
|
+
|
|
490
|
+
The ${type} body data would be encrypted as "AES-128(256) / CBC mode / PKCS#5 Padding / Base64 Encoding", through the ${component} component.
|
|
491
|
+
|
|
492
|
+
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.
|
|
493
|
+
|
|
494
|
+
-----------------
|
|
495
|
+
|
|
496
|
+
`;
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
interface ISchemaTuple {
|
|
502
|
+
metadata: Metadata;
|
|
503
|
+
schema: IJsonSchema;
|
|
504
|
+
}
|