@nestia/sdk 2.4.2 → 2.4.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.
Files changed (111) hide show
  1. package/lib/NestiaSdkApplication.js +2 -6
  2. package/lib/NestiaSdkApplication.js.map +1 -1
  3. package/lib/analyses/AccessorAnalyzer.js.map +1 -1
  4. package/lib/analyses/ConfigAnalyzer.js +4 -8
  5. package/lib/analyses/ConfigAnalyzer.js.map +1 -1
  6. package/lib/analyses/ControllerAnalyzer.js +6 -8
  7. package/lib/analyses/ControllerAnalyzer.js.map +1 -1
  8. package/lib/analyses/ExceptionAnalyzer.js.map +1 -1
  9. package/lib/analyses/GenericAnalyzer.js +1 -2
  10. package/lib/analyses/GenericAnalyzer.js.map +1 -1
  11. package/lib/analyses/ImportAnalyzer.js +4 -4
  12. package/lib/analyses/ImportAnalyzer.js.map +1 -1
  13. package/lib/analyses/PathAnalyzer.js.map +1 -1
  14. package/lib/analyses/ReflectAnalyzer.js +7 -8
  15. package/lib/analyses/ReflectAnalyzer.js.map +1 -1
  16. package/lib/analyses/SecurityAnalyzer.js.map +1 -1
  17. package/lib/executable/internal/CommandParser.js.map +1 -1
  18. package/lib/executable/internal/NestiaConfigLoader.js.map +1 -1
  19. package/lib/executable/internal/NestiaSdkCommand.js.map +1 -1
  20. package/lib/executable/sdk.js +11 -11
  21. package/lib/executable/sdk.js.map +1 -1
  22. package/lib/generates/E2eGenerator.js.map +1 -1
  23. package/lib/generates/SdkGenerator.js.map +1 -1
  24. package/lib/generates/SwaggerGenerator.js +5 -11
  25. package/lib/generates/SwaggerGenerator.js.map +1 -1
  26. package/lib/generates/internal/E2eFileProgrammer.js +2 -8
  27. package/lib/generates/internal/E2eFileProgrammer.js.map +1 -1
  28. package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
  29. package/lib/generates/internal/SdkDtoGenerator.js +3 -9
  30. package/lib/generates/internal/SdkDtoGenerator.js.map +1 -1
  31. package/lib/generates/internal/SdkFileProgrammer.js +4 -4
  32. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  33. package/lib/generates/internal/SdkFunctionProgrammer.js +12 -20
  34. package/lib/generates/internal/SdkFunctionProgrammer.js.map +1 -1
  35. package/lib/generates/internal/SdkImportWizard.js.map +1 -1
  36. package/lib/generates/internal/SdkRouteDirectory.js +1 -3
  37. package/lib/generates/internal/SdkRouteDirectory.js.map +1 -1
  38. package/lib/generates/internal/SdkSimulationProgrammer.js +5 -7
  39. package/lib/generates/internal/SdkSimulationProgrammer.js.map +1 -1
  40. package/lib/generates/internal/SdkTypeDefiner.js +2 -5
  41. package/lib/generates/internal/SdkTypeDefiner.js.map +1 -1
  42. package/lib/generates/internal/SwaggerSchemaGenerator.js +29 -44
  43. package/lib/generates/internal/SwaggerSchemaGenerator.js.map +1 -1
  44. package/lib/generates/internal/SwaggerSchemaValidator.js +3 -9
  45. package/lib/generates/internal/SwaggerSchemaValidator.js.map +1 -1
  46. package/lib/structures/MethodType.js +1 -7
  47. package/lib/structures/MethodType.js.map +1 -1
  48. package/lib/structures/TypeEntry.js.map +1 -1
  49. package/lib/utils/ArrayUtil.js.map +1 -1
  50. package/lib/utils/FileRetriever.js.map +1 -1
  51. package/lib/utils/ImportDictionary.js +1 -4
  52. package/lib/utils/ImportDictionary.js.map +1 -1
  53. package/lib/utils/MapUtil.js.map +1 -1
  54. package/lib/utils/PathUtil.js.map +1 -1
  55. package/lib/utils/SourceFinder.js.map +1 -1
  56. package/package.json +4 -7
  57. package/src/INestiaConfig.ts +234 -234
  58. package/src/NestiaSdkApplication.ts +253 -268
  59. package/src/analyses/AccessorAnalyzer.ts +60 -60
  60. package/src/analyses/ConfigAnalyzer.ts +147 -164
  61. package/src/analyses/ControllerAnalyzer.ts +379 -399
  62. package/src/analyses/ExceptionAnalyzer.ts +115 -124
  63. package/src/analyses/GenericAnalyzer.ts +51 -57
  64. package/src/analyses/ImportAnalyzer.ts +138 -159
  65. package/src/analyses/PathAnalyzer.ts +98 -100
  66. package/src/analyses/ReflectAnalyzer.ts +425 -433
  67. package/src/analyses/SecurityAnalyzer.ts +20 -20
  68. package/src/executable/internal/CommandParser.ts +15 -15
  69. package/src/executable/internal/NestiaConfigLoader.ts +67 -68
  70. package/src/executable/internal/NestiaSdkCommand.ts +60 -64
  71. package/src/executable/sdk.ts +73 -73
  72. package/src/generates/E2eGenerator.ts +64 -67
  73. package/src/generates/SdkGenerator.ts +96 -100
  74. package/src/generates/SwaggerGenerator.ts +372 -410
  75. package/src/generates/internal/E2eFileProgrammer.ts +123 -129
  76. package/src/generates/internal/SdkDistributionComposer.ts +91 -91
  77. package/src/generates/internal/SdkDtoGenerator.ts +424 -450
  78. package/src/generates/internal/SdkFileProgrammer.ts +106 -111
  79. package/src/generates/internal/SdkFunctionProgrammer.ts +466 -501
  80. package/src/generates/internal/SdkImportWizard.ts +55 -55
  81. package/src/generates/internal/SdkRouteDirectory.ts +17 -19
  82. package/src/generates/internal/SdkSimulationProgrammer.ts +133 -142
  83. package/src/generates/internal/SdkTypeDefiner.ts +119 -124
  84. package/src/generates/internal/SwaggerSchemaGenerator.ts +382 -401
  85. package/src/generates/internal/SwaggerSchemaValidator.ts +198 -210
  86. package/src/index.ts +4 -4
  87. package/src/module.ts +2 -2
  88. package/src/structures/IController.ts +79 -81
  89. package/src/structures/IErrorReport.ts +6 -6
  90. package/src/structures/INestiaProject.ts +13 -13
  91. package/src/structures/INormalizedInput.ts +20 -20
  92. package/src/structures/IRoute.ts +40 -41
  93. package/src/structures/ISwagger.ts +91 -91
  94. package/src/structures/ISwaggerComponents.ts +29 -29
  95. package/src/structures/ISwaggerError.ts +8 -8
  96. package/src/structures/ISwaggerInfo.ts +80 -80
  97. package/src/structures/ISwaggerLazyProperty.ts +7 -7
  98. package/src/structures/ISwaggerLazySchema.ts +7 -7
  99. package/src/structures/ISwaggerRoute.ts +51 -51
  100. package/src/structures/ISwaggerSecurityScheme.ts +65 -65
  101. package/src/structures/ITypeTuple.ts +6 -6
  102. package/src/structures/MethodType.ts +5 -11
  103. package/src/structures/ParamCategory.ts +1 -1
  104. package/src/structures/TypeEntry.ts +22 -22
  105. package/src/utils/ArrayUtil.ts +26 -26
  106. package/src/utils/FileRetriever.ts +22 -22
  107. package/src/utils/ImportDictionary.ts +125 -128
  108. package/src/utils/MapUtil.ts +14 -14
  109. package/src/utils/PathUtil.ts +10 -10
  110. package/src/utils/SourceFinder.ts +66 -70
  111. package/src/utils/StripEnums.ts +5 -10
@@ -1,410 +1,372 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { Singleton } from "tstl/thread/Singleton";
4
- import ts from "typescript";
5
-
6
- import typia, { IJsonApplication, IJsonComponents } from "typia";
7
- import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
8
- import { JsonApplicationProgrammer } from "typia/lib/programmers/json/JsonApplicationProgrammer";
9
-
10
- import { INestiaConfig } from "../INestiaConfig";
11
- import { IRoute } from "../structures/IRoute";
12
- import { ISwagger } from "../structures/ISwagger";
13
- import { ISwaggerError } from "../structures/ISwaggerError";
14
- import { ISwaggerInfo } from "../structures/ISwaggerInfo";
15
- import { ISwaggerLazyProperty } from "../structures/ISwaggerLazyProperty";
16
- import { ISwaggerLazySchema } from "../structures/ISwaggerLazySchema";
17
- import { ISwaggerRoute } from "../structures/ISwaggerRoute";
18
- import { ISwaggerSecurityScheme } from "../structures/ISwaggerSecurityScheme";
19
- import { FileRetriever } from "../utils/FileRetriever";
20
- import { MapUtil } from "../utils/MapUtil";
21
- import { SwaggerSchemaGenerator } from "./internal/SwaggerSchemaGenerator";
22
-
23
- export namespace SwaggerGenerator {
24
- export interface IProps {
25
- config: INestiaConfig.ISwaggerConfig;
26
- checker: ts.TypeChecker;
27
- collection: MetadataCollection;
28
- lazySchemas: Array<ISwaggerLazySchema>;
29
- lazyProperties: Array<ISwaggerLazyProperty>;
30
- errors: ISwaggerError[];
31
- }
32
-
33
- export const generate =
34
- (checker: ts.TypeChecker) =>
35
- (config: INestiaConfig.ISwaggerConfig) =>
36
- async (routeList: IRoute[]): Promise<void> => {
37
- console.log("Generating Swagger Documents");
38
-
39
- // VALIDATE SECURITY
40
- validate_security(config)(routeList);
41
-
42
- // PREPARE ASSETS
43
- const parsed: path.ParsedPath = path.parse(config.output);
44
- const directory: string = path.dirname(parsed.dir);
45
- if (fs.existsSync(directory) === false)
46
- try {
47
- await fs.promises.mkdir(directory);
48
- } catch {}
49
- if (fs.existsSync(directory) === false)
50
- throw new Error(
51
- `Error on NestiaApplication.swagger(): failed to create output directory: ${directory}`,
52
- );
53
-
54
- const location: string = !!parsed.ext
55
- ? path.resolve(config.output)
56
- : path.join(path.resolve(config.output), "swagger.json");
57
-
58
- const collection: MetadataCollection = new MetadataCollection({
59
- replace: MetadataCollection.replace,
60
- });
61
-
62
- // CONSTRUCT SWAGGER DOCUMENTS
63
- const errors: ISwaggerError[] = [];
64
- const lazySchemas: Array<ISwaggerLazySchema> = [];
65
- const lazyProperties: Array<ISwaggerLazyProperty> = [];
66
- const swagger: ISwagger = await initialize(config);
67
- const pathDict: Map<
68
- string,
69
- Record<string, ISwaggerRoute>
70
- > = new Map();
71
-
72
- for (const route of routeList) {
73
- if (route.jsDocTags.find((tag) => tag.name === "internal"))
74
- continue;
75
-
76
- const path: Record<string, ISwaggerRoute> = MapUtil.take(
77
- pathDict,
78
- get_path(route.path, route.parameters),
79
- () => ({}),
80
- );
81
- path[route.method.toLowerCase()] = generate_route({
82
- config,
83
- checker,
84
- collection,
85
- lazySchemas,
86
- lazyProperties,
87
- errors,
88
- })(route);
89
- }
90
- swagger.paths = {};
91
- for (const [path, routes] of pathDict) swagger.paths[path] = routes;
92
-
93
- // FILL JSON-SCHEMAS
94
- const application: IJsonApplication =
95
- JsonApplicationProgrammer.write({
96
- purpose: "swagger",
97
- })(lazySchemas.map(({ metadata }) => metadata));
98
- swagger.components = {
99
- ...(swagger.components ?? {}),
100
- ...(application.components ?? {}),
101
- };
102
- lazySchemas.forEach(({ schema }, index) => {
103
- Object.assign(schema, application.schemas[index]!);
104
- });
105
- for (const p of lazyProperties)
106
- Object.assign(
107
- p.schema,
108
- (
109
- application.components.schemas?.[
110
- p.object
111
- ] as IJsonComponents.IObject
112
- )?.properties[p.property],
113
- );
114
-
115
- // CONFIGURE SECURITY
116
- if (config.security) fill_security(config.security, swagger);
117
-
118
- // REPORT ERRORS
119
- if (errors.length) {
120
- for (const e of errors)
121
- console.error(
122
- `${path.relative(e.route.location, process.cwd())}:${
123
- e.route.symbol.class
124
- }.${e.route.symbol.function}:${
125
- e.from
126
- } - error TS(@nestia/sdk): invalid type detected.\n\n` +
127
- e.messages.map((m) => ` - ${m}`).join("\n"),
128
- "\n\n",
129
- );
130
- throw new TypeError("Invalid type detected");
131
- }
132
-
133
- // DO GENERATE
134
- await fs.promises.writeFile(
135
- location,
136
- !config.beautify
137
- ? JSON.stringify(swagger)
138
- : JSON.stringify(
139
- swagger,
140
- null,
141
- typeof config.beautify === "number"
142
- ? config.beautify
143
- : 2,
144
- ),
145
- "utf8",
146
- );
147
- };
148
-
149
- const validate_security =
150
- (config: INestiaConfig.ISwaggerConfig) =>
151
- (routeList: IRoute[]): void | never => {
152
- const securityMap: Map<
153
- string,
154
- { scheme: ISwaggerSecurityScheme; scopes: Set<string> }
155
- > = new Map();
156
- for (const [key, value] of Object.entries(config.security ?? {}))
157
- securityMap.set(key, {
158
- scheme: emend_security(value),
159
- scopes:
160
- value.type === "oauth2"
161
- ? new Set([
162
- ...Object.keys(
163
- value.flows.authorizationCode?.scopes ??
164
- {},
165
- ),
166
- ...Object.keys(
167
- value.flows.implicit?.scopes ?? {},
168
- ),
169
- ...Object.keys(
170
- value.flows.password?.scopes ?? {},
171
- ),
172
- ...Object.keys(
173
- value.flows.clientCredentials?.scopes ??
174
- {},
175
- ),
176
- ])
177
- : new Set(),
178
- });
179
-
180
- const validate =
181
- (reporter: (str: string) => void) =>
182
- (key: string, scopes: string[]) => {
183
- const security = securityMap.get(key);
184
- if (security === undefined)
185
- return reporter(
186
- `target security scheme "${key}" does not exists.`,
187
- );
188
- else if (scopes.length === 0) return;
189
- else if (security.scheme.type !== "oauth2")
190
- return reporter(
191
- `target security scheme "${key}" is not "oauth2" type, but you've configured the scopes.`,
192
- );
193
- for (const s of scopes)
194
- if (security.scopes.has(s) === false)
195
- reporter(
196
- `target security scheme "${key}" does not have a specific scope "${s}".`,
197
- );
198
- };
199
-
200
- const violations: string[] = [];
201
- for (const route of routeList)
202
- for (const record of route.security)
203
- for (const [key, scopes] of Object.entries(record))
204
- validate((str) =>
205
- violations.push(
206
- ` - ${str} (${route.symbol} at "${route.location}")`,
207
- ),
208
- )(key, scopes);
209
-
210
- if (violations.length)
211
- throw new Error(
212
- `Error on NestiaApplication.swagger(): invalid security specification. Check your "nestia.config.ts" file's "swagger.security" property, or each controller methods.\n` +
213
- `\n` +
214
- `List of violations:\n` +
215
- violations.join("\n"),
216
- );
217
- };
218
-
219
- /* ---------------------------------------------------------
220
- INITIALIZERS
221
- --------------------------------------------------------- */
222
- const initialize = async (
223
- config: INestiaConfig.ISwaggerConfig,
224
- ): Promise<ISwagger> => {
225
- const pack = new Singleton(
226
- async (): Promise<Partial<ISwaggerInfo> | null> => {
227
- const location: string | null = await FileRetriever.file(
228
- "package.json",
229
- )(process.cwd());
230
- if (location === null) return null;
231
-
232
- try {
233
- const content: string = await fs.promises.readFile(
234
- location,
235
- "utf8",
236
- );
237
- const data = typia.json.assertParse<{
238
- name?: string;
239
- version?: string;
240
- description?: string;
241
- license?:
242
- | string
243
- | {
244
- type: string;
245
- /**
246
- * @format url
247
- */
248
- url: string;
249
- };
250
- }>(content);
251
- return {
252
- title: data.name,
253
- version: data.version,
254
- description: data.description,
255
- license: data.license
256
- ? typeof data.license === "string"
257
- ? { name: data.license }
258
- : typeof data.license === "object"
259
- ? {
260
- name: data.license.type,
261
- url: data.license.url,
262
- }
263
- : undefined
264
- : undefined,
265
- };
266
- } catch {
267
- return null;
268
- }
269
- },
270
- );
271
-
272
- return {
273
- openapi: "3.0.1",
274
- servers: config.servers ?? [
275
- {
276
- url: "https://github.com/samchon/nestia",
277
- description: "insert your server url",
278
- },
279
- ],
280
- info: {
281
- ...(config.info ?? {}),
282
- version:
283
- config.info?.version ??
284
- (await pack.get())?.version ??
285
- "0.1.0",
286
- title:
287
- config.info?.title ??
288
- (await pack.get())?.title ??
289
- "Swagger Documents",
290
- description:
291
- config.info?.description ??
292
- (await pack.get())?.description ??
293
- "Generated by nestia - https://github.com/samchon/nestia",
294
- license: config.info?.license ?? (await pack.get())?.license,
295
- },
296
- paths: {},
297
- components: {},
298
- };
299
- };
300
-
301
- function get_path(path: string, parameters: IRoute.IParameter[]): string {
302
- const filtered: IRoute.IParameter[] = parameters.filter(
303
- (param) => param.category === "param" && !!param.field,
304
- );
305
- for (const param of filtered)
306
- path = path.replace(`:${param.field}`, `{${param.field}}`);
307
- return path;
308
- }
309
-
310
- const generate_route =
311
- (props: IProps) =>
312
- (route: IRoute): ISwaggerRoute => {
313
- const body = route.parameters.find(
314
- (param) => param.category === "body",
315
- );
316
- const getJsDocTexts = (name: string) =>
317
- route.jsDocTags
318
- .filter(
319
- (tag) =>
320
- tag.name === name &&
321
- tag.text &&
322
- tag.text.find(
323
- (elem) =>
324
- elem.kind === "text" && elem.text.length,
325
- ) !== undefined,
326
- )
327
- .map(
328
- (tag) =>
329
- tag.text!.find((elem) => elem.kind === "text")!
330
- .text,
331
- );
332
-
333
- const description: string | undefined = route.description?.length
334
- ? route.description
335
- : undefined;
336
- const summary: string | undefined = (() => {
337
- if (description === undefined) return undefined;
338
-
339
- const [explicit] = getJsDocTexts("summary");
340
- if (explicit?.length) return explicit;
341
-
342
- const index: number = description.indexOf(".");
343
- if (index <= 0) return undefined;
344
-
345
- const content: string = description.substring(0, index).trim();
346
- return content.length ? content : undefined;
347
- })();
348
- const deprecated = route.jsDocTags.find(
349
- (tag) => tag.name === "deprecated",
350
- );
351
-
352
- return {
353
- deprecated: deprecated ? true : undefined,
354
- tags: [
355
- ...route.swaggerTags,
356
- ...new Set([...getJsDocTexts("tag")]),
357
- ],
358
- operationId:
359
- route.operationId ??
360
- props.config.operationId?.({
361
- class: route.symbol.class,
362
- function: route.symbol.function,
363
- method: route.method as "GET",
364
- path: route.path,
365
- }),
366
- parameters: route.parameters
367
- .filter((param) => param.category !== "body")
368
- .map((param) =>
369
- SwaggerSchemaGenerator.parameter(props)(route)(param),
370
- )
371
- .flat(),
372
- requestBody: body
373
- ? SwaggerSchemaGenerator.body(props)(route)(body)
374
- : undefined,
375
- responses: SwaggerSchemaGenerator.response(props)(route),
376
- summary,
377
- description,
378
- security: route.security.length ? route.security : undefined,
379
- "x-nestia-namespace": [
380
- ...route.path
381
- .split("/")
382
- .filter((str) => str.length && str[0] !== ":"),
383
- route.name,
384
- ].join("."),
385
- "x-nestia-jsDocTags": route.jsDocTags,
386
- "x-nestia-method": route.method,
387
- };
388
- };
389
-
390
- function fill_security(
391
- security: Required<INestiaConfig.ISwaggerConfig>["security"],
392
- swagger: ISwagger,
393
- ): void {
394
- swagger.components.securitySchemes = {};
395
- for (const [key, value] of Object.entries(security))
396
- swagger.components.securitySchemes[key] = emend_security(value);
397
- }
398
-
399
- function emend_security(
400
- input: ISwaggerSecurityScheme,
401
- ): ISwaggerSecurityScheme {
402
- if (input.type === "apiKey")
403
- return {
404
- ...input,
405
- in: input.in ?? "header",
406
- name: input.name ?? "Authorization",
407
- };
408
- return input;
409
- }
410
- }
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { Singleton } from "tstl/thread/Singleton";
4
+ import ts from "typescript";
5
+ import typia, { IJsonApplication, IJsonComponents } from "typia";
6
+ import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
7
+ import { JsonApplicationProgrammer } from "typia/lib/programmers/json/JsonApplicationProgrammer";
8
+
9
+ import { INestiaConfig } from "../INestiaConfig";
10
+ import { IRoute } from "../structures/IRoute";
11
+ import { ISwagger } from "../structures/ISwagger";
12
+ import { ISwaggerError } from "../structures/ISwaggerError";
13
+ import { ISwaggerInfo } from "../structures/ISwaggerInfo";
14
+ import { ISwaggerLazyProperty } from "../structures/ISwaggerLazyProperty";
15
+ import { ISwaggerLazySchema } from "../structures/ISwaggerLazySchema";
16
+ import { ISwaggerRoute } from "../structures/ISwaggerRoute";
17
+ import { ISwaggerSecurityScheme } from "../structures/ISwaggerSecurityScheme";
18
+ import { FileRetriever } from "../utils/FileRetriever";
19
+ import { MapUtil } from "../utils/MapUtil";
20
+ import { SwaggerSchemaGenerator } from "./internal/SwaggerSchemaGenerator";
21
+
22
+ export namespace SwaggerGenerator {
23
+ export interface IProps {
24
+ config: INestiaConfig.ISwaggerConfig;
25
+ checker: ts.TypeChecker;
26
+ collection: MetadataCollection;
27
+ lazySchemas: Array<ISwaggerLazySchema>;
28
+ lazyProperties: Array<ISwaggerLazyProperty>;
29
+ errors: ISwaggerError[];
30
+ }
31
+
32
+ export const generate =
33
+ (checker: ts.TypeChecker) =>
34
+ (config: INestiaConfig.ISwaggerConfig) =>
35
+ async (routeList: IRoute[]): Promise<void> => {
36
+ console.log("Generating Swagger Documents");
37
+
38
+ // VALIDATE SECURITY
39
+ validate_security(config)(routeList);
40
+
41
+ // PREPARE ASSETS
42
+ const parsed: path.ParsedPath = path.parse(config.output);
43
+ const directory: string = path.dirname(parsed.dir);
44
+ if (fs.existsSync(directory) === false)
45
+ try {
46
+ await fs.promises.mkdir(directory);
47
+ } catch {}
48
+ if (fs.existsSync(directory) === false)
49
+ throw new Error(
50
+ `Error on NestiaApplication.swagger(): failed to create output directory: ${directory}`,
51
+ );
52
+
53
+ const location: string = !!parsed.ext
54
+ ? path.resolve(config.output)
55
+ : path.join(path.resolve(config.output), "swagger.json");
56
+
57
+ const collection: MetadataCollection = new MetadataCollection({
58
+ replace: MetadataCollection.replace,
59
+ });
60
+
61
+ // CONSTRUCT SWAGGER DOCUMENTS
62
+ const errors: ISwaggerError[] = [];
63
+ const lazySchemas: Array<ISwaggerLazySchema> = [];
64
+ const lazyProperties: Array<ISwaggerLazyProperty> = [];
65
+ const swagger: ISwagger = await initialize(config);
66
+ const pathDict: Map<string, Record<string, ISwaggerRoute>> = new Map();
67
+
68
+ for (const route of routeList) {
69
+ if (route.jsDocTags.find((tag) => tag.name === "internal")) continue;
70
+
71
+ const path: Record<string, ISwaggerRoute> = MapUtil.take(
72
+ pathDict,
73
+ get_path(route.path, route.parameters),
74
+ () => ({}),
75
+ );
76
+ path[route.method.toLowerCase()] = generate_route({
77
+ config,
78
+ checker,
79
+ collection,
80
+ lazySchemas,
81
+ lazyProperties,
82
+ errors,
83
+ })(route);
84
+ }
85
+ swagger.paths = {};
86
+ for (const [path, routes] of pathDict) swagger.paths[path] = routes;
87
+
88
+ // FILL JSON-SCHEMAS
89
+ const application: IJsonApplication = JsonApplicationProgrammer.write({
90
+ purpose: "swagger",
91
+ })(lazySchemas.map(({ metadata }) => metadata));
92
+ swagger.components = {
93
+ ...(swagger.components ?? {}),
94
+ ...(application.components ?? {}),
95
+ };
96
+ lazySchemas.forEach(({ schema }, index) => {
97
+ Object.assign(schema, application.schemas[index]!);
98
+ });
99
+ for (const p of lazyProperties)
100
+ Object.assign(
101
+ p.schema,
102
+ (
103
+ application.components.schemas?.[
104
+ p.object
105
+ ] as IJsonComponents.IObject
106
+ )?.properties[p.property],
107
+ );
108
+
109
+ // CONFIGURE SECURITY
110
+ if (config.security) fill_security(config.security, swagger);
111
+
112
+ // REPORT ERRORS
113
+ if (errors.length) {
114
+ for (const e of errors)
115
+ console.error(
116
+ `${path.relative(e.route.location, process.cwd())}:${
117
+ e.route.symbol.class
118
+ }.${e.route.symbol.function}:${
119
+ e.from
120
+ } - error TS(@nestia/sdk): invalid type detected.\n\n` +
121
+ e.messages.map((m) => ` - ${m}`).join("\n"),
122
+ "\n\n",
123
+ );
124
+ throw new TypeError("Invalid type detected");
125
+ }
126
+
127
+ // DO GENERATE
128
+ await fs.promises.writeFile(
129
+ location,
130
+ !config.beautify
131
+ ? JSON.stringify(swagger)
132
+ : JSON.stringify(
133
+ swagger,
134
+ null,
135
+ typeof config.beautify === "number" ? config.beautify : 2,
136
+ ),
137
+ "utf8",
138
+ );
139
+ };
140
+
141
+ const validate_security =
142
+ (config: INestiaConfig.ISwaggerConfig) =>
143
+ (routeList: IRoute[]): void | never => {
144
+ const securityMap: Map<
145
+ string,
146
+ { scheme: ISwaggerSecurityScheme; scopes: Set<string> }
147
+ > = new Map();
148
+ for (const [key, value] of Object.entries(config.security ?? {}))
149
+ securityMap.set(key, {
150
+ scheme: emend_security(value),
151
+ scopes:
152
+ value.type === "oauth2"
153
+ ? new Set([
154
+ ...Object.keys(value.flows.authorizationCode?.scopes ?? {}),
155
+ ...Object.keys(value.flows.implicit?.scopes ?? {}),
156
+ ...Object.keys(value.flows.password?.scopes ?? {}),
157
+ ...Object.keys(value.flows.clientCredentials?.scopes ?? {}),
158
+ ])
159
+ : new Set(),
160
+ });
161
+
162
+ const validate =
163
+ (reporter: (str: string) => void) =>
164
+ (key: string, scopes: string[]) => {
165
+ const security = securityMap.get(key);
166
+ if (security === undefined)
167
+ return reporter(`target security scheme "${key}" does not exists.`);
168
+ else if (scopes.length === 0) return;
169
+ else if (security.scheme.type !== "oauth2")
170
+ return reporter(
171
+ `target security scheme "${key}" is not "oauth2" type, but you've configured the scopes.`,
172
+ );
173
+ for (const s of scopes)
174
+ if (security.scopes.has(s) === false)
175
+ reporter(
176
+ `target security scheme "${key}" does not have a specific scope "${s}".`,
177
+ );
178
+ };
179
+
180
+ const violations: string[] = [];
181
+ for (const route of routeList)
182
+ for (const record of route.security)
183
+ for (const [key, scopes] of Object.entries(record))
184
+ validate((str) =>
185
+ violations.push(
186
+ ` - ${str} (${route.symbol} at "${route.location}")`,
187
+ ),
188
+ )(key, scopes);
189
+
190
+ if (violations.length)
191
+ throw new Error(
192
+ `Error on NestiaApplication.swagger(): invalid security specification. Check your "nestia.config.ts" file's "swagger.security" property, or each controller methods.\n` +
193
+ `\n` +
194
+ `List of violations:\n` +
195
+ violations.join("\n"),
196
+ );
197
+ };
198
+
199
+ /* ---------------------------------------------------------
200
+ INITIALIZERS
201
+ --------------------------------------------------------- */
202
+ const initialize = async (
203
+ config: INestiaConfig.ISwaggerConfig,
204
+ ): Promise<ISwagger> => {
205
+ const pack = new Singleton(
206
+ async (): Promise<Partial<ISwaggerInfo> | null> => {
207
+ const location: string | null = await FileRetriever.file(
208
+ "package.json",
209
+ )(process.cwd());
210
+ if (location === null) return null;
211
+
212
+ try {
213
+ const content: string = await fs.promises.readFile(location, "utf8");
214
+ const data = typia.json.assertParse<{
215
+ name?: string;
216
+ version?: string;
217
+ description?: string;
218
+ license?:
219
+ | string
220
+ | {
221
+ type: string;
222
+ /**
223
+ * @format url
224
+ */
225
+ url: string;
226
+ };
227
+ }>(content);
228
+ return {
229
+ title: data.name,
230
+ version: data.version,
231
+ description: data.description,
232
+ license: data.license
233
+ ? typeof data.license === "string"
234
+ ? { name: data.license }
235
+ : typeof data.license === "object"
236
+ ? {
237
+ name: data.license.type,
238
+ url: data.license.url,
239
+ }
240
+ : undefined
241
+ : undefined,
242
+ };
243
+ } catch {
244
+ return null;
245
+ }
246
+ },
247
+ );
248
+
249
+ return {
250
+ openapi: "3.0.1",
251
+ servers: config.servers ?? [
252
+ {
253
+ url: "https://github.com/samchon/nestia",
254
+ description: "insert your server url",
255
+ },
256
+ ],
257
+ info: {
258
+ ...(config.info ?? {}),
259
+ version: config.info?.version ?? (await pack.get())?.version ?? "0.1.0",
260
+ title:
261
+ config.info?.title ??
262
+ (await pack.get())?.title ??
263
+ "Swagger Documents",
264
+ description:
265
+ config.info?.description ??
266
+ (await pack.get())?.description ??
267
+ "Generated by nestia - https://github.com/samchon/nestia",
268
+ license: config.info?.license ?? (await pack.get())?.license,
269
+ },
270
+ paths: {},
271
+ components: {},
272
+ };
273
+ };
274
+
275
+ function get_path(path: string, parameters: IRoute.IParameter[]): string {
276
+ const filtered: IRoute.IParameter[] = parameters.filter(
277
+ (param) => param.category === "param" && !!param.field,
278
+ );
279
+ for (const param of filtered)
280
+ path = path.replace(`:${param.field}`, `{${param.field}}`);
281
+ return path;
282
+ }
283
+
284
+ const generate_route =
285
+ (props: IProps) =>
286
+ (route: IRoute): ISwaggerRoute => {
287
+ const body = route.parameters.find((param) => param.category === "body");
288
+ const getJsDocTexts = (name: string) =>
289
+ route.jsDocTags
290
+ .filter(
291
+ (tag) =>
292
+ tag.name === name &&
293
+ tag.text &&
294
+ tag.text.find(
295
+ (elem) => elem.kind === "text" && elem.text.length,
296
+ ) !== undefined,
297
+ )
298
+ .map((tag) => tag.text!.find((elem) => elem.kind === "text")!.text);
299
+
300
+ const description: string | undefined = route.description?.length
301
+ ? route.description
302
+ : undefined;
303
+ const summary: string | undefined = (() => {
304
+ if (description === undefined) return undefined;
305
+
306
+ const [explicit] = getJsDocTexts("summary");
307
+ if (explicit?.length) return explicit;
308
+
309
+ const index: number = description.indexOf(".");
310
+ if (index <= 0) return undefined;
311
+
312
+ const content: string = description.substring(0, index).trim();
313
+ return content.length ? content : undefined;
314
+ })();
315
+ const deprecated = route.jsDocTags.find(
316
+ (tag) => tag.name === "deprecated",
317
+ );
318
+
319
+ return {
320
+ deprecated: deprecated ? true : undefined,
321
+ tags: [...route.swaggerTags, ...new Set([...getJsDocTexts("tag")])],
322
+ operationId:
323
+ route.operationId ??
324
+ props.config.operationId?.({
325
+ class: route.symbol.class,
326
+ function: route.symbol.function,
327
+ method: route.method as "GET",
328
+ path: route.path,
329
+ }),
330
+ parameters: route.parameters
331
+ .filter((param) => param.category !== "body")
332
+ .map((param) => SwaggerSchemaGenerator.parameter(props)(route)(param))
333
+ .flat(),
334
+ requestBody: body
335
+ ? SwaggerSchemaGenerator.body(props)(route)(body)
336
+ : undefined,
337
+ responses: SwaggerSchemaGenerator.response(props)(route),
338
+ summary,
339
+ description,
340
+ security: route.security.length ? route.security : undefined,
341
+ "x-nestia-namespace": [
342
+ ...route.path
343
+ .split("/")
344
+ .filter((str) => str.length && str[0] !== ":"),
345
+ route.name,
346
+ ].join("."),
347
+ "x-nestia-jsDocTags": route.jsDocTags,
348
+ "x-nestia-method": route.method,
349
+ };
350
+ };
351
+
352
+ function fill_security(
353
+ security: Required<INestiaConfig.ISwaggerConfig>["security"],
354
+ swagger: ISwagger,
355
+ ): void {
356
+ swagger.components.securitySchemes = {};
357
+ for (const [key, value] of Object.entries(security))
358
+ swagger.components.securitySchemes[key] = emend_security(value);
359
+ }
360
+
361
+ function emend_security(
362
+ input: ISwaggerSecurityScheme,
363
+ ): ISwaggerSecurityScheme {
364
+ if (input.type === "apiKey")
365
+ return {
366
+ ...input,
367
+ in: input.in ?? "header",
368
+ name: input.name ?? "Authorization",
369
+ };
370
+ return input;
371
+ }
372
+ }