@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
|
@@ -3,22 +3,31 @@ import NodePath from "path";
|
|
|
3
3
|
import { Singleton } from "tstl/thread/Singleton";
|
|
4
4
|
import ts from "typescript";
|
|
5
5
|
|
|
6
|
-
import typia, { IJsonApplication
|
|
6
|
+
import typia, { IJsonApplication } from "typia";
|
|
7
7
|
import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
|
|
8
|
-
import {
|
|
9
|
-
import { Metadata } from "typia/lib/metadata/Metadata";
|
|
10
|
-
import { ApplicationProgrammer } from "typia/lib/programmers/ApplicationProgrammer";
|
|
8
|
+
import { JsonApplicationProgrammer } from "typia/lib/programmers/json/JsonApplicationProgrammer";
|
|
11
9
|
|
|
12
10
|
import { INestiaConfig } from "../INestiaConfig";
|
|
13
11
|
import { IRoute } from "../structures/IRoute";
|
|
14
12
|
import { ISwagger } from "../structures/ISwagger";
|
|
13
|
+
import { ISwaggerError } from "../structures/ISwaggerError";
|
|
15
14
|
import { ISwaggerInfo } from "../structures/ISwaggerInfo";
|
|
16
15
|
import { ISwaggerRoute } from "../structures/ISwaggerRoute";
|
|
16
|
+
import { ISwaggerSchemaTuple } from "../structures/ISwaggerSchemaTuple";
|
|
17
17
|
import { ISwaggerSecurityScheme } from "../structures/ISwaggerSecurityScheme";
|
|
18
18
|
import { FileRetriever } from "../utils/FileRetriever";
|
|
19
19
|
import { MapUtil } from "../utils/MapUtil";
|
|
20
|
+
import { SwaggerSchemaGenerator } from "./internal/SwaggerSchemaGenerator";
|
|
20
21
|
|
|
21
22
|
export namespace SwaggerGenerator {
|
|
23
|
+
export interface IProps {
|
|
24
|
+
config: INestiaConfig.ISwaggerConfig;
|
|
25
|
+
checker: ts.TypeChecker;
|
|
26
|
+
collection: MetadataCollection;
|
|
27
|
+
tuples: Array<ISwaggerSchemaTuple>;
|
|
28
|
+
errors: ISwaggerError[];
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
export const generate =
|
|
23
32
|
(checker: ts.TypeChecker) =>
|
|
24
33
|
(config: INestiaConfig.ISwaggerConfig) =>
|
|
@@ -52,7 +61,8 @@ export namespace SwaggerGenerator {
|
|
|
52
61
|
});
|
|
53
62
|
|
|
54
63
|
// CONSTRUCT SWAGGER DOCUMENTS
|
|
55
|
-
const
|
|
64
|
+
const errors: ISwaggerError[] = [];
|
|
65
|
+
const tuples: Array<ISwaggerSchemaTuple> = [];
|
|
56
66
|
const swagger: ISwagger = await initialize(config);
|
|
57
67
|
const pathDict: Map<
|
|
58
68
|
string,
|
|
@@ -67,13 +77,13 @@ export namespace SwaggerGenerator {
|
|
|
67
77
|
get_path(route.path, route.parameters),
|
|
68
78
|
() => ({}),
|
|
69
79
|
);
|
|
70
|
-
path[route.method.toLowerCase()] = generate_route(
|
|
80
|
+
path[route.method.toLowerCase()] = generate_route({
|
|
71
81
|
config,
|
|
72
82
|
checker,
|
|
73
83
|
collection,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
84
|
+
tuples,
|
|
85
|
+
errors,
|
|
86
|
+
})(route);
|
|
77
87
|
}
|
|
78
88
|
swagger.paths = {};
|
|
79
89
|
for (const [path, routes] of pathDict) {
|
|
@@ -81,14 +91,15 @@ export namespace SwaggerGenerator {
|
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
// FILL JSON-SCHEMAS
|
|
84
|
-
const application: IJsonApplication =
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
const application: IJsonApplication =
|
|
95
|
+
JsonApplicationProgrammer.write({
|
|
96
|
+
purpose: "swagger",
|
|
97
|
+
})(tuples.map(({ metadata }) => metadata));
|
|
87
98
|
swagger.components = {
|
|
88
99
|
...(swagger.components ?? {}),
|
|
89
100
|
...(application.components ?? {}),
|
|
90
101
|
};
|
|
91
|
-
|
|
102
|
+
tuples.forEach(({ schema }, index) => {
|
|
92
103
|
Object.assign(schema, application.schemas[index]!);
|
|
93
104
|
});
|
|
94
105
|
|
|
@@ -191,7 +202,7 @@ export namespace SwaggerGenerator {
|
|
|
191
202
|
location,
|
|
192
203
|
"utf8",
|
|
193
204
|
);
|
|
194
|
-
const data = typia.assertParse<{
|
|
205
|
+
const data = typia.json.assertParse<{
|
|
195
206
|
name?: string;
|
|
196
207
|
version?: string;
|
|
197
208
|
description?: string;
|
|
@@ -264,93 +275,74 @@ export namespace SwaggerGenerator {
|
|
|
264
275
|
return path;
|
|
265
276
|
}
|
|
266
277
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
(tag) =>
|
|
290
|
-
tag.text!.find((elem) => elem.kind === "text")!.text,
|
|
291
|
-
);
|
|
278
|
+
const generate_route =
|
|
279
|
+
(props: IProps) =>
|
|
280
|
+
(route: IRoute): ISwaggerRoute => {
|
|
281
|
+
const body = route.parameters.find(
|
|
282
|
+
(param) => param.category === "body",
|
|
283
|
+
);
|
|
284
|
+
const getTagTexts = (name: string) =>
|
|
285
|
+
route.tags
|
|
286
|
+
.filter(
|
|
287
|
+
(tag) =>
|
|
288
|
+
tag.name === name &&
|
|
289
|
+
tag.text &&
|
|
290
|
+
tag.text.find(
|
|
291
|
+
(elem) =>
|
|
292
|
+
elem.kind === "text" && elem.text.length,
|
|
293
|
+
) !== undefined,
|
|
294
|
+
)
|
|
295
|
+
.map(
|
|
296
|
+
(tag) =>
|
|
297
|
+
tag.text!.find((elem) => elem.kind === "text")!
|
|
298
|
+
.text,
|
|
299
|
+
);
|
|
292
300
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
301
|
+
const description: string | undefined = route.description?.length
|
|
302
|
+
? route.description
|
|
303
|
+
: undefined;
|
|
304
|
+
const summary: string | undefined = (() => {
|
|
305
|
+
if (description === undefined) return undefined;
|
|
298
306
|
|
|
299
|
-
|
|
300
|
-
|
|
307
|
+
const [explicit] = getTagTexts("summary");
|
|
308
|
+
if (explicit?.length) return explicit;
|
|
301
309
|
|
|
302
|
-
|
|
303
|
-
|
|
310
|
+
const index: number = description.indexOf(".");
|
|
311
|
+
if (index <= 0) return undefined;
|
|
304
312
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
313
|
+
const content: string = description.substring(0, index).trim();
|
|
314
|
+
return content.length ? content : undefined;
|
|
315
|
+
})();
|
|
316
|
+
const deprecated = route.tags.find(
|
|
317
|
+
(tag) => tag.name === "deprecated",
|
|
318
|
+
);
|
|
309
319
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
checker,
|
|
319
|
-
collection,
|
|
320
|
-
tupleList,
|
|
321
|
-
route,
|
|
322
|
-
param,
|
|
320
|
+
return {
|
|
321
|
+
deprecated: deprecated ? true : undefined,
|
|
322
|
+
tags: getTagTexts("tag"),
|
|
323
|
+
operationId: route.operationId,
|
|
324
|
+
parameters: route.parameters
|
|
325
|
+
.filter((param) => param.category !== "body")
|
|
326
|
+
.map((param) =>
|
|
327
|
+
SwaggerSchemaGenerator.parameter(props)(route)(param),
|
|
323
328
|
),
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
),
|
|
341
|
-
summary,
|
|
342
|
-
description,
|
|
343
|
-
security: route.security.length ? route.security : undefined,
|
|
344
|
-
"x-nestia-namespace": [
|
|
345
|
-
...route.path
|
|
346
|
-
.split("/")
|
|
347
|
-
.filter((str) => str.length && str[0] !== ":"),
|
|
348
|
-
route.name,
|
|
349
|
-
].join("."),
|
|
350
|
-
"x-nestia-jsDocTags": route.tags,
|
|
351
|
-
"x-nestia-method": route.method,
|
|
329
|
+
requestBody: body
|
|
330
|
+
? SwaggerSchemaGenerator.body(props)(route)(body)
|
|
331
|
+
: undefined,
|
|
332
|
+
responses: SwaggerSchemaGenerator.response(props)(route),
|
|
333
|
+
summary,
|
|
334
|
+
description,
|
|
335
|
+
security: route.security.length ? route.security : undefined,
|
|
336
|
+
"x-nestia-namespace": [
|
|
337
|
+
...route.path
|
|
338
|
+
.split("/")
|
|
339
|
+
.filter((str) => str.length && str[0] !== ":"),
|
|
340
|
+
route.name,
|
|
341
|
+
].join("."),
|
|
342
|
+
"x-nestia-jsDocTags": route.tags,
|
|
343
|
+
"x-nestia-method": route.method,
|
|
344
|
+
};
|
|
352
345
|
};
|
|
353
|
-
}
|
|
354
346
|
|
|
355
347
|
function fill_security(
|
|
356
348
|
security: Required<INestiaConfig.ISwaggerConfig>["security"],
|
|
@@ -372,388 +364,4 @@ export namespace SwaggerGenerator {
|
|
|
372
364
|
};
|
|
373
365
|
return input;
|
|
374
366
|
}
|
|
375
|
-
|
|
376
|
-
/* ---------------------------------------------------------
|
|
377
|
-
REQUEST & RESPONSE
|
|
378
|
-
--------------------------------------------------------- */
|
|
379
|
-
function generate_parameter(
|
|
380
|
-
config: INestiaConfig.ISwaggerConfig,
|
|
381
|
-
checker: ts.TypeChecker,
|
|
382
|
-
collection: MetadataCollection,
|
|
383
|
-
tupleList: Array<ISchemaTuple>,
|
|
384
|
-
route: IRoute,
|
|
385
|
-
parameter: IRoute.IParameter,
|
|
386
|
-
): ISwaggerRoute.IParameter[] {
|
|
387
|
-
const schema: IJsonSchema | null = generate_schema(
|
|
388
|
-
checker,
|
|
389
|
-
collection,
|
|
390
|
-
tupleList,
|
|
391
|
-
parameter.type.type,
|
|
392
|
-
);
|
|
393
|
-
if (schema === null)
|
|
394
|
-
throw new Error(
|
|
395
|
-
`Error on NestiaApplication.swagger(): invalid parameter type on ${route.symbol}#${parameter.name}`,
|
|
396
|
-
);
|
|
397
|
-
else if (
|
|
398
|
-
parameter.custom &&
|
|
399
|
-
parameter.category === "param" &&
|
|
400
|
-
!!parameter.meta &&
|
|
401
|
-
(parameter.meta.type === "date" ||
|
|
402
|
-
parameter.meta.type === "uuid") &&
|
|
403
|
-
schema !== null
|
|
404
|
-
) {
|
|
405
|
-
const string: IJsonSchema.IString = schema as IJsonSchema.IString;
|
|
406
|
-
string.format = parameter.meta.type;
|
|
407
|
-
} else if (
|
|
408
|
-
config.decompose === true &&
|
|
409
|
-
parameter.category === "query"
|
|
410
|
-
) {
|
|
411
|
-
const metadata: Metadata = MetadataFactory.analyze(checker)({
|
|
412
|
-
resolve: true,
|
|
413
|
-
constant: true,
|
|
414
|
-
absorb: true,
|
|
415
|
-
validate: (meta) => {
|
|
416
|
-
if (meta.atomics.find((str) => str === "bigint"))
|
|
417
|
-
throw new Error(NO_BIGIT);
|
|
418
|
-
},
|
|
419
|
-
})(collection)(parameter.type.type);
|
|
420
|
-
if (
|
|
421
|
-
metadata.size() === 1 &&
|
|
422
|
-
metadata.objects.length === 1 &&
|
|
423
|
-
metadata.objects[0].properties.every(
|
|
424
|
-
(prop) =>
|
|
425
|
-
prop.key.size() &&
|
|
426
|
-
prop.key.constants.length === 1 &&
|
|
427
|
-
prop.key.constants[0].type === "string" &&
|
|
428
|
-
route.parameters.every(
|
|
429
|
-
(param) =>
|
|
430
|
-
param.name !== prop.key.constants[0].values[0],
|
|
431
|
-
),
|
|
432
|
-
)
|
|
433
|
-
) {
|
|
434
|
-
const app: IJsonApplication = ApplicationProgrammer.write({
|
|
435
|
-
purpose: "swagger",
|
|
436
|
-
})([metadata]);
|
|
437
|
-
const top = Object.values(app.components.schemas ?? {})[0];
|
|
438
|
-
|
|
439
|
-
if (typia.is<IJsonComponents.IObject>(top))
|
|
440
|
-
return Object.entries(top.properties).map(
|
|
441
|
-
([key, value]) => ({
|
|
442
|
-
name: key,
|
|
443
|
-
in: "query",
|
|
444
|
-
schema: value,
|
|
445
|
-
required: top.required?.includes(key) ?? false,
|
|
446
|
-
description: value.description,
|
|
447
|
-
}),
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
} else if (
|
|
451
|
-
config.decompose === true &&
|
|
452
|
-
parameter.category === "headers"
|
|
453
|
-
) {
|
|
454
|
-
const metadata: Metadata = MetadataFactory.analyze(checker)({
|
|
455
|
-
resolve: true,
|
|
456
|
-
constant: true,
|
|
457
|
-
absorb: true,
|
|
458
|
-
validate: (meta) => {
|
|
459
|
-
if (meta.atomics.find((str) => str === "bigint"))
|
|
460
|
-
throw new Error(NO_BIGIT);
|
|
461
|
-
},
|
|
462
|
-
})(collection)(parameter.type.type);
|
|
463
|
-
if (
|
|
464
|
-
metadata.size() === 1 &&
|
|
465
|
-
metadata.objects.length === 1 &&
|
|
466
|
-
metadata.objects[0].properties.every(
|
|
467
|
-
(prop) =>
|
|
468
|
-
prop.key.size() &&
|
|
469
|
-
prop.key.constants.length === 1 &&
|
|
470
|
-
prop.key.constants[0].type === "string" &&
|
|
471
|
-
route.parameters.every(
|
|
472
|
-
(param) =>
|
|
473
|
-
param.name !== prop.key.constants[0].values[0],
|
|
474
|
-
),
|
|
475
|
-
)
|
|
476
|
-
) {
|
|
477
|
-
const app: IJsonApplication = ApplicationProgrammer.write({
|
|
478
|
-
purpose: "swagger",
|
|
479
|
-
})([metadata]);
|
|
480
|
-
const top = Object.values(app.components.schemas ?? {})[0];
|
|
481
|
-
|
|
482
|
-
if (typia.is<IJsonComponents.IObject>(top))
|
|
483
|
-
return Object.entries(top.properties).map(
|
|
484
|
-
([key, value]) => ({
|
|
485
|
-
name: key,
|
|
486
|
-
in: "header",
|
|
487
|
-
schema: value,
|
|
488
|
-
required: top.required?.includes(key) ?? false,
|
|
489
|
-
description: value.description,
|
|
490
|
-
}),
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return [
|
|
496
|
-
{
|
|
497
|
-
name: parameter.field ?? parameter.name,
|
|
498
|
-
in:
|
|
499
|
-
parameter.category === "param"
|
|
500
|
-
? "path"
|
|
501
|
-
: parameter.category === "headers"
|
|
502
|
-
? "header"
|
|
503
|
-
: parameter.category,
|
|
504
|
-
description:
|
|
505
|
-
get_parametric_description(
|
|
506
|
-
route,
|
|
507
|
-
"param",
|
|
508
|
-
parameter.name,
|
|
509
|
-
) || "",
|
|
510
|
-
schema,
|
|
511
|
-
required: required(parameter.type.type),
|
|
512
|
-
},
|
|
513
|
-
];
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function generate_request_body(
|
|
517
|
-
checker: ts.TypeChecker,
|
|
518
|
-
collection: MetadataCollection,
|
|
519
|
-
tupleList: Array<ISchemaTuple>,
|
|
520
|
-
route: IRoute,
|
|
521
|
-
parameter: IRoute.IParameter,
|
|
522
|
-
): ISwaggerRoute.IRequestBody {
|
|
523
|
-
const schema: IJsonSchema | null = generate_schema(
|
|
524
|
-
checker,
|
|
525
|
-
collection,
|
|
526
|
-
tupleList,
|
|
527
|
-
parameter.type.type,
|
|
528
|
-
);
|
|
529
|
-
if (schema === null)
|
|
530
|
-
throw new Error(
|
|
531
|
-
`Error on NestiaApplication.sdk(): invalid request body type on ${route.symbol}.`,
|
|
532
|
-
);
|
|
533
|
-
else if (parameter.category !== "body")
|
|
534
|
-
throw new Error("Unreachable code.");
|
|
535
|
-
|
|
536
|
-
const contentType = parameter.custom
|
|
537
|
-
? parameter.contentType
|
|
538
|
-
: "application/json";
|
|
539
|
-
const description = get_parametric_description(
|
|
540
|
-
route,
|
|
541
|
-
"param",
|
|
542
|
-
parameter.name,
|
|
543
|
-
);
|
|
544
|
-
|
|
545
|
-
return {
|
|
546
|
-
description:
|
|
547
|
-
parameter.custom && parameter.encrypted
|
|
548
|
-
? `${warning.get(!!description).get("request")}${
|
|
549
|
-
description ?? ""
|
|
550
|
-
}`
|
|
551
|
-
: description,
|
|
552
|
-
content: {
|
|
553
|
-
[contentType]: {
|
|
554
|
-
schema,
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
required: true,
|
|
558
|
-
"x-nestia-encrypted": parameter.custom && parameter.encrypted,
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function generate_response_body(
|
|
563
|
-
checker: ts.TypeChecker,
|
|
564
|
-
collection: MetadataCollection,
|
|
565
|
-
tupleList: Array<ISchemaTuple>,
|
|
566
|
-
route: IRoute,
|
|
567
|
-
): ISwaggerRoute.IResponseBody {
|
|
568
|
-
const output: ISwaggerRoute.IResponseBody = {};
|
|
569
|
-
|
|
570
|
-
//----
|
|
571
|
-
// EXCEPTION STATUSES
|
|
572
|
-
//----
|
|
573
|
-
// FROM DECORATOR
|
|
574
|
-
for (const [status, exp] of Object.entries(route.exceptions)) {
|
|
575
|
-
const schema = generate_schema(
|
|
576
|
-
checker,
|
|
577
|
-
collection,
|
|
578
|
-
tupleList,
|
|
579
|
-
exp.type,
|
|
580
|
-
);
|
|
581
|
-
if (schema !== null)
|
|
582
|
-
output[status] = {
|
|
583
|
-
description: exp.description ?? "",
|
|
584
|
-
content: {
|
|
585
|
-
"application/json": { schema },
|
|
586
|
-
},
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// FROM COMMENT TAGS
|
|
591
|
-
for (const tag of route.tags) {
|
|
592
|
-
if (tag.name !== "throw" && tag.name !== "throws") continue;
|
|
593
|
-
|
|
594
|
-
const text: string | undefined = tag.text?.find(
|
|
595
|
-
(elem) => elem.kind === "text",
|
|
596
|
-
)?.text;
|
|
597
|
-
if (text === undefined) continue;
|
|
598
|
-
|
|
599
|
-
const elements: string[] = text.split(" ").map((str) => str.trim());
|
|
600
|
-
const status: string = elements[0];
|
|
601
|
-
if (
|
|
602
|
-
isNaN(Number(status)) &&
|
|
603
|
-
status !== "2XX" &&
|
|
604
|
-
status !== "3XX" &&
|
|
605
|
-
status !== "4XX" &&
|
|
606
|
-
status !== "5XX"
|
|
607
|
-
)
|
|
608
|
-
continue;
|
|
609
|
-
|
|
610
|
-
const description: string | undefined =
|
|
611
|
-
elements.length === 1 ? undefined : elements.slice(1).join(" ");
|
|
612
|
-
const oldbie = output[status];
|
|
613
|
-
if (description && oldbie !== undefined)
|
|
614
|
-
oldbie.description = description;
|
|
615
|
-
else if (oldbie === undefined)
|
|
616
|
-
output[status] = {
|
|
617
|
-
description: description ?? "",
|
|
618
|
-
content: {
|
|
619
|
-
"application/json": {
|
|
620
|
-
schema: {},
|
|
621
|
-
},
|
|
622
|
-
},
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
//----
|
|
627
|
-
// SUCCESS
|
|
628
|
-
//----
|
|
629
|
-
// STATUS & SCHEMA
|
|
630
|
-
const status: string =
|
|
631
|
-
route.status !== undefined
|
|
632
|
-
? String(route.status)
|
|
633
|
-
: route.method === "GET" ||
|
|
634
|
-
route.method === "HEAD" ||
|
|
635
|
-
route.method === "DELETE"
|
|
636
|
-
? "200"
|
|
637
|
-
: "201";
|
|
638
|
-
const schema: IJsonSchema | null = generate_schema(
|
|
639
|
-
checker,
|
|
640
|
-
collection,
|
|
641
|
-
tupleList,
|
|
642
|
-
route.output.type,
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
// DO ASSIGN
|
|
646
|
-
const description =
|
|
647
|
-
get_parametric_description(route, "return") ??
|
|
648
|
-
get_parametric_description(route, "returns");
|
|
649
|
-
output[status] = {
|
|
650
|
-
description: route.encrypted
|
|
651
|
-
? `${warning.get(!!description).get("response", route.method)}${
|
|
652
|
-
description ?? ""
|
|
653
|
-
}`
|
|
654
|
-
: description ?? "",
|
|
655
|
-
content:
|
|
656
|
-
schema === null || route.output.name === "void"
|
|
657
|
-
? undefined
|
|
658
|
-
: {
|
|
659
|
-
[route.output.contentType]: {
|
|
660
|
-
schema,
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
"x-nestia-encrypted": route.encrypted,
|
|
664
|
-
};
|
|
665
|
-
return output;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/* ---------------------------------------------------------
|
|
669
|
-
UTILS
|
|
670
|
-
--------------------------------------------------------- */
|
|
671
|
-
function generate_schema(
|
|
672
|
-
checker: ts.TypeChecker,
|
|
673
|
-
collection: MetadataCollection,
|
|
674
|
-
tupleList: Array<ISchemaTuple>,
|
|
675
|
-
type: ts.Type,
|
|
676
|
-
): IJsonSchema | null {
|
|
677
|
-
const metadata: Metadata = MetadataFactory.analyze(checker)({
|
|
678
|
-
resolve: true,
|
|
679
|
-
constant: true,
|
|
680
|
-
absorb: false,
|
|
681
|
-
validate: (meta) => {
|
|
682
|
-
if (meta.atomics.find((str) => str === "bigint"))
|
|
683
|
-
throw new Error(NO_BIGIT);
|
|
684
|
-
},
|
|
685
|
-
})(collection)(type);
|
|
686
|
-
if (metadata.empty() && metadata.nullable === false) return null;
|
|
687
|
-
|
|
688
|
-
const schema: IJsonSchema = {} as IJsonSchema;
|
|
689
|
-
tupleList.push({ metadata, schema });
|
|
690
|
-
return schema;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function get_parametric_description(
|
|
694
|
-
route: IRoute,
|
|
695
|
-
tagName: string,
|
|
696
|
-
parameterName?: string,
|
|
697
|
-
): string | undefined {
|
|
698
|
-
const parametric: (elem: ts.JSDocTagInfo) => boolean = parameterName
|
|
699
|
-
? (tag) =>
|
|
700
|
-
tag.text!.find(
|
|
701
|
-
(elem) =>
|
|
702
|
-
elem.kind === "parameterName" &&
|
|
703
|
-
elem.text === parameterName,
|
|
704
|
-
) !== undefined
|
|
705
|
-
: () => true;
|
|
706
|
-
|
|
707
|
-
const tag: ts.JSDocTagInfo | undefined = route.tags.find(
|
|
708
|
-
(tag) => tag.name === tagName && tag.text && parametric(tag),
|
|
709
|
-
);
|
|
710
|
-
return tag && tag.text
|
|
711
|
-
? tag.text.find((elem) => elem.kind === "text")?.text
|
|
712
|
-
: undefined;
|
|
713
|
-
}
|
|
714
367
|
}
|
|
715
|
-
|
|
716
|
-
const required = (type: ts.Type): boolean => {
|
|
717
|
-
if (type.isUnion()) return type.types.every((type) => required(type));
|
|
718
|
-
const obstacle = (other: ts.TypeFlags) => (type.getFlags() & other) === 0;
|
|
719
|
-
return (
|
|
720
|
-
obstacle(ts.TypeFlags.Undefined) &&
|
|
721
|
-
obstacle(ts.TypeFlags.Never) &&
|
|
722
|
-
obstacle(ts.TypeFlags.Void) &&
|
|
723
|
-
obstacle(ts.TypeFlags.VoidLike)
|
|
724
|
-
);
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
const warning = new Singleton((described: boolean) => {
|
|
728
|
-
return new Singleton((type: "request" | "response", method?: string) => {
|
|
729
|
-
const summary =
|
|
730
|
-
type === "request"
|
|
731
|
-
? "Request body must be encrypted."
|
|
732
|
-
: "Response data have been encrypted.";
|
|
733
|
-
const component =
|
|
734
|
-
type === "request"
|
|
735
|
-
? "[EncryptedBody](https://github.com/samchon/@nestia/core#encryptedbody)"
|
|
736
|
-
: `[EncryptedRoute.${method![0].toUpperCase()}.${method!
|
|
737
|
-
.substring(1)
|
|
738
|
-
.toLowerCase()}](https://github.com/samchon/@nestia/core#encryptedroute)`;
|
|
739
|
-
|
|
740
|
-
const content: string[] = [
|
|
741
|
-
"## Warning",
|
|
742
|
-
"",
|
|
743
|
-
summary,
|
|
744
|
-
"",
|
|
745
|
-
`The ${type} body data would be encrypted as "AES-128(256) / CBC mode / PKCS#5 Padding / Base64 Encoding", through the ${component} component.`,
|
|
746
|
-
"",
|
|
747
|
-
`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.`,
|
|
748
|
-
];
|
|
749
|
-
if (described === true) content.push("----------------", "");
|
|
750
|
-
return content.join("\n");
|
|
751
|
-
});
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
interface ISchemaTuple {
|
|
755
|
-
metadata: Metadata;
|
|
756
|
-
schema: IJsonSchema;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
const NO_BIGIT = "Error on typia.application(): does not allow bigint type.";
|