@nestia/sdk 1.0.4 → 1.0.5

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