@nestia/sdk 2.0.0-dev.20230831-5 → 2.0.0-dev.20230901-2
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 -22
- 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 +37 -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 +13 -11
- 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 +196 -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/lib/utils/ImportDictionary.d.ts +1 -2
- package/lib/utils/ImportDictionary.js +28 -24
- package/lib/utils/ImportDictionary.js.map +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 +102 -478
- package/src/generates/internal/E2eFileProgrammer.ts +7 -49
- package/src/generates/internal/SdkFunctionProgrammer.ts +14 -12
- package/src/generates/internal/SdkSimulationProgrammer.ts +5 -5
- package/src/generates/internal/SwaggerSchemaGenerator.ts +433 -0
- package/src/generates/internal/SwaggerSchemaValidator.ts +216 -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
- package/src/utils/ImportDictionary.ts +29 -26
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import NodePath from "path";
|
|
3
|
+
import path from "path";
|
|
3
4
|
import { Singleton } from "tstl/thread/Singleton";
|
|
4
5
|
import ts from "typescript";
|
|
5
6
|
|
|
6
|
-
import typia, { IJsonApplication
|
|
7
|
+
import typia, { IJsonApplication } from "typia";
|
|
7
8
|
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";
|
|
9
|
+
import { JsonApplicationProgrammer } from "typia/lib/programmers/json/JsonApplicationProgrammer";
|
|
11
10
|
|
|
12
11
|
import { INestiaConfig } from "../INestiaConfig";
|
|
13
12
|
import { IRoute } from "../structures/IRoute";
|
|
14
13
|
import { ISwagger } from "../structures/ISwagger";
|
|
14
|
+
import { ISwaggerError } from "../structures/ISwaggerError";
|
|
15
15
|
import { ISwaggerInfo } from "../structures/ISwaggerInfo";
|
|
16
16
|
import { ISwaggerRoute } from "../structures/ISwaggerRoute";
|
|
17
|
+
import { ISwaggerSchemaTuple } from "../structures/ISwaggerSchemaTuple";
|
|
17
18
|
import { ISwaggerSecurityScheme } from "../structures/ISwaggerSecurityScheme";
|
|
18
19
|
import { FileRetriever } from "../utils/FileRetriever";
|
|
19
20
|
import { MapUtil } from "../utils/MapUtil";
|
|
21
|
+
import { SwaggerSchemaGenerator } from "./internal/SwaggerSchemaGenerator";
|
|
20
22
|
|
|
21
23
|
export namespace SwaggerGenerator {
|
|
24
|
+
export interface IProps {
|
|
25
|
+
config: INestiaConfig.ISwaggerConfig;
|
|
26
|
+
checker: ts.TypeChecker;
|
|
27
|
+
collection: MetadataCollection;
|
|
28
|
+
tuples: Array<ISwaggerSchemaTuple>;
|
|
29
|
+
errors: ISwaggerError[];
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
export const generate =
|
|
23
33
|
(checker: ts.TypeChecker) =>
|
|
24
34
|
(config: INestiaConfig.ISwaggerConfig) =>
|
|
@@ -52,7 +62,8 @@ export namespace SwaggerGenerator {
|
|
|
52
62
|
});
|
|
53
63
|
|
|
54
64
|
// CONSTRUCT SWAGGER DOCUMENTS
|
|
55
|
-
const
|
|
65
|
+
const errors: ISwaggerError[] = [];
|
|
66
|
+
const tuples: Array<ISwaggerSchemaTuple> = [];
|
|
56
67
|
const swagger: ISwagger = await initialize(config);
|
|
57
68
|
const pathDict: Map<
|
|
58
69
|
string,
|
|
@@ -67,13 +78,13 @@ export namespace SwaggerGenerator {
|
|
|
67
78
|
get_path(route.path, route.parameters),
|
|
68
79
|
() => ({}),
|
|
69
80
|
);
|
|
70
|
-
path[route.method.toLowerCase()] = generate_route(
|
|
81
|
+
path[route.method.toLowerCase()] = generate_route({
|
|
71
82
|
config,
|
|
72
83
|
checker,
|
|
73
84
|
collection,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
85
|
+
tuples,
|
|
86
|
+
errors,
|
|
87
|
+
})(route);
|
|
77
88
|
}
|
|
78
89
|
swagger.paths = {};
|
|
79
90
|
for (const [path, routes] of pathDict) {
|
|
@@ -81,20 +92,36 @@ export namespace SwaggerGenerator {
|
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
// FILL JSON-SCHEMAS
|
|
84
|
-
const application: IJsonApplication =
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
const application: IJsonApplication =
|
|
96
|
+
JsonApplicationProgrammer.write({
|
|
97
|
+
purpose: "swagger",
|
|
98
|
+
})(tuples.map(({ metadata }) => metadata));
|
|
87
99
|
swagger.components = {
|
|
88
100
|
...(swagger.components ?? {}),
|
|
89
101
|
...(application.components ?? {}),
|
|
90
102
|
};
|
|
91
|
-
|
|
103
|
+
tuples.forEach(({ schema }, index) => {
|
|
92
104
|
Object.assign(schema, application.schemas[index]!);
|
|
93
105
|
});
|
|
94
106
|
|
|
95
107
|
// CONFIGURE SECURITY
|
|
96
108
|
if (config.security) fill_security(config.security, swagger);
|
|
97
109
|
|
|
110
|
+
// REPORT ERRORS
|
|
111
|
+
if (errors.length) {
|
|
112
|
+
for (const e of errors)
|
|
113
|
+
console.error(
|
|
114
|
+
`${path.relative(location, process.cwd())}:${
|
|
115
|
+
e.route.symbol
|
|
116
|
+
}:${
|
|
117
|
+
e.from
|
|
118
|
+
} - error TS(@nestia/sdk): invalid type detected.\n\n` +
|
|
119
|
+
e.messages.map((m) => ` - ${m}`).join("\n"),
|
|
120
|
+
"\n\n",
|
|
121
|
+
);
|
|
122
|
+
throw new TypeError("Invalid type detected");
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
// DO GENERATE
|
|
99
126
|
await fs.promises.writeFile(
|
|
100
127
|
location,
|
|
@@ -191,7 +218,7 @@ export namespace SwaggerGenerator {
|
|
|
191
218
|
location,
|
|
192
219
|
"utf8",
|
|
193
220
|
);
|
|
194
|
-
const data = typia.assertParse<{
|
|
221
|
+
const data = typia.json.assertParse<{
|
|
195
222
|
name?: string;
|
|
196
223
|
version?: string;
|
|
197
224
|
description?: string;
|
|
@@ -264,93 +291,74 @@ export namespace SwaggerGenerator {
|
|
|
264
291
|
return path;
|
|
265
292
|
}
|
|
266
293
|
|
|
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
|
-
);
|
|
294
|
+
const generate_route =
|
|
295
|
+
(props: IProps) =>
|
|
296
|
+
(route: IRoute): ISwaggerRoute => {
|
|
297
|
+
const body = route.parameters.find(
|
|
298
|
+
(param) => param.category === "body",
|
|
299
|
+
);
|
|
300
|
+
const getTagTexts = (name: string) =>
|
|
301
|
+
route.tags
|
|
302
|
+
.filter(
|
|
303
|
+
(tag) =>
|
|
304
|
+
tag.name === name &&
|
|
305
|
+
tag.text &&
|
|
306
|
+
tag.text.find(
|
|
307
|
+
(elem) =>
|
|
308
|
+
elem.kind === "text" && elem.text.length,
|
|
309
|
+
) !== undefined,
|
|
310
|
+
)
|
|
311
|
+
.map(
|
|
312
|
+
(tag) =>
|
|
313
|
+
tag.text!.find((elem) => elem.kind === "text")!
|
|
314
|
+
.text,
|
|
315
|
+
);
|
|
292
316
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
317
|
+
const description: string | undefined = route.description?.length
|
|
318
|
+
? route.description
|
|
319
|
+
: undefined;
|
|
320
|
+
const summary: string | undefined = (() => {
|
|
321
|
+
if (description === undefined) return undefined;
|
|
298
322
|
|
|
299
|
-
|
|
300
|
-
|
|
323
|
+
const [explicit] = getTagTexts("summary");
|
|
324
|
+
if (explicit?.length) return explicit;
|
|
301
325
|
|
|
302
|
-
|
|
303
|
-
|
|
326
|
+
const index: number = description.indexOf(".");
|
|
327
|
+
if (index <= 0) return undefined;
|
|
304
328
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
329
|
+
const content: string = description.substring(0, index).trim();
|
|
330
|
+
return content.length ? content : undefined;
|
|
331
|
+
})();
|
|
332
|
+
const deprecated = route.tags.find(
|
|
333
|
+
(tag) => tag.name === "deprecated",
|
|
334
|
+
);
|
|
309
335
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
checker,
|
|
319
|
-
collection,
|
|
320
|
-
tupleList,
|
|
321
|
-
route,
|
|
322
|
-
param,
|
|
336
|
+
return {
|
|
337
|
+
deprecated: deprecated ? true : undefined,
|
|
338
|
+
tags: getTagTexts("tag"),
|
|
339
|
+
operationId: route.operationId,
|
|
340
|
+
parameters: route.parameters
|
|
341
|
+
.filter((param) => param.category !== "body")
|
|
342
|
+
.map((param) =>
|
|
343
|
+
SwaggerSchemaGenerator.parameter(props)(route)(param),
|
|
323
344
|
),
|
|
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,
|
|
345
|
+
requestBody: body
|
|
346
|
+
? SwaggerSchemaGenerator.body(props)(route)(body)
|
|
347
|
+
: undefined,
|
|
348
|
+
responses: SwaggerSchemaGenerator.response(props)(route),
|
|
349
|
+
summary,
|
|
350
|
+
description,
|
|
351
|
+
security: route.security.length ? route.security : undefined,
|
|
352
|
+
"x-nestia-namespace": [
|
|
353
|
+
...route.path
|
|
354
|
+
.split("/")
|
|
355
|
+
.filter((str) => str.length && str[0] !== ":"),
|
|
356
|
+
route.name,
|
|
357
|
+
].join("."),
|
|
358
|
+
"x-nestia-jsDocTags": route.tags,
|
|
359
|
+
"x-nestia-method": route.method,
|
|
360
|
+
};
|
|
352
361
|
};
|
|
353
|
-
}
|
|
354
362
|
|
|
355
363
|
function fill_security(
|
|
356
364
|
security: Required<INestiaConfig.ISwaggerConfig>["security"],
|
|
@@ -372,388 +380,4 @@ export namespace SwaggerGenerator {
|
|
|
372
380
|
};
|
|
373
381
|
return input;
|
|
374
382
|
}
|
|
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
383
|
}
|
|
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.";
|