@nestia/sdk 2.4.5 → 2.4.6

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 (66) hide show
  1. package/lib/analyses/ConfigAnalyzer.js +1 -1
  2. package/lib/analyses/ConfigAnalyzer.js.map +1 -1
  3. package/lib/analyses/ControllerAnalyzer.js.map +1 -1
  4. package/lib/analyses/PathAnalyzer.js.map +1 -1
  5. package/lib/analyses/ReflectAnalyzer.js.map +1 -1
  6. package/lib/executable/sdk.js +11 -11
  7. package/lib/generates/SwaggerGenerator.js.map +1 -1
  8. package/lib/generates/internal/SdkFunctionProgrammer.js +7 -7
  9. package/lib/generates/internal/SdkSimulationProgrammer.js.map +1 -1
  10. package/lib/generates/internal/SwaggerSchemaGenerator.js.map +1 -1
  11. package/package.json +3 -3
  12. package/src/INestiaConfig.ts +248 -248
  13. package/src/NestiaSdkApplication.ts +253 -253
  14. package/src/analyses/AccessorAnalyzer.ts +60 -60
  15. package/src/analyses/ConfigAnalyzer.ts +3 -3
  16. package/src/analyses/ControllerAnalyzer.ts +2 -2
  17. package/src/analyses/ExceptionAnalyzer.ts +115 -115
  18. package/src/analyses/GenericAnalyzer.ts +51 -51
  19. package/src/analyses/ImportAnalyzer.ts +138 -138
  20. package/src/analyses/PathAnalyzer.ts +14 -14
  21. package/src/analyses/ReflectAnalyzer.ts +9 -9
  22. package/src/analyses/SecurityAnalyzer.ts +20 -20
  23. package/src/executable/internal/CommandParser.ts +15 -15
  24. package/src/executable/internal/NestiaConfigLoader.ts +67 -67
  25. package/src/executable/internal/NestiaSdkCommand.ts +60 -60
  26. package/src/executable/sdk.ts +73 -73
  27. package/src/generates/E2eGenerator.ts +64 -64
  28. package/src/generates/SdkGenerator.ts +96 -96
  29. package/src/generates/SwaggerGenerator.ts +5 -5
  30. package/src/generates/internal/E2eFileProgrammer.ts +123 -123
  31. package/src/generates/internal/SdkDistributionComposer.ts +91 -91
  32. package/src/generates/internal/SdkDtoGenerator.ts +424 -424
  33. package/src/generates/internal/SdkFileProgrammer.ts +106 -106
  34. package/src/generates/internal/SdkFunctionProgrammer.ts +518 -518
  35. package/src/generates/internal/SdkImportWizard.ts +55 -55
  36. package/src/generates/internal/SdkRouteDirectory.ts +17 -17
  37. package/src/generates/internal/SdkSimulationProgrammer.ts +4 -4
  38. package/src/generates/internal/SdkTypeDefiner.ts +119 -119
  39. package/src/generates/internal/SwaggerSchemaGenerator.ts +16 -16
  40. package/src/generates/internal/SwaggerSchemaValidator.ts +198 -198
  41. package/src/index.ts +4 -4
  42. package/src/module.ts +2 -2
  43. package/src/structures/IController.ts +91 -91
  44. package/src/structures/IErrorReport.ts +6 -6
  45. package/src/structures/INestiaProject.ts +13 -13
  46. package/src/structures/INormalizedInput.ts +20 -20
  47. package/src/structures/IRoute.ts +52 -52
  48. package/src/structures/ISwagger.ts +91 -91
  49. package/src/structures/ISwaggerComponents.ts +29 -29
  50. package/src/structures/ISwaggerError.ts +8 -8
  51. package/src/structures/ISwaggerInfo.ts +80 -80
  52. package/src/structures/ISwaggerLazyProperty.ts +7 -7
  53. package/src/structures/ISwaggerLazySchema.ts +7 -7
  54. package/src/structures/ISwaggerRoute.ts +51 -51
  55. package/src/structures/ISwaggerSecurityScheme.ts +65 -65
  56. package/src/structures/ITypeTuple.ts +6 -6
  57. package/src/structures/MethodType.ts +5 -5
  58. package/src/structures/ParamCategory.ts +1 -1
  59. package/src/structures/TypeEntry.ts +22 -22
  60. package/src/utils/ArrayUtil.ts +26 -26
  61. package/src/utils/FileRetriever.ts +22 -22
  62. package/src/utils/ImportDictionary.ts +125 -125
  63. package/src/utils/MapUtil.ts +14 -14
  64. package/src/utils/PathUtil.ts +10 -10
  65. package/src/utils/SourceFinder.ts +66 -66
  66. package/src/utils/StripEnums.ts +5 -5
@@ -1,518 +1,518 @@
1
- import { Pair } from "tstl/utility/Pair";
2
- import { IJsDocTagInfo } from "typia/lib/schemas/metadata/IJsDocTagInfo";
3
- import { Escaper } from "typia/lib/utils/Escaper";
4
-
5
- import { INestiaConfig } from "../../INestiaConfig";
6
- import { IController } from "../../structures/IController";
7
- import { IRoute } from "../../structures/IRoute";
8
- import { ImportDictionary } from "../../utils/ImportDictionary";
9
- import { SdkDtoGenerator } from "./SdkDtoGenerator";
10
- import { SdkImportWizard } from "./SdkImportWizard";
11
- import { SdkSimulationProgrammer } from "./SdkSimulationProgrammer";
12
- import { SdkTypeDefiner } from "./SdkTypeDefiner";
13
-
14
- export namespace SdkFunctionProgrammer {
15
- export const generate =
16
- (config: INestiaConfig) =>
17
- (importer: ImportDictionary) =>
18
- (route: IRoute): string => {
19
- const [x, y, z] = [head, body, tail].map((closure) =>
20
- closure(config)(importer)(route)({
21
- headers: route.parameters.find(
22
- (param) =>
23
- param.category === "headers" && param.field === undefined,
24
- ),
25
- query: route.parameters.find(
26
- (param) => param.category === "query" && param.field === undefined,
27
- ),
28
- input: route.parameters.find((param) => param.category === "body"),
29
- }),
30
- );
31
- return `${x} ${y}\n${z}`;
32
- };
33
-
34
- /* ---------------------------------------------------------
35
- BODY
36
- --------------------------------------------------------- */
37
- const body =
38
- (config: INestiaConfig) =>
39
- (importer: ImportDictionary) =>
40
- (route: IRoute) =>
41
- (props: {
42
- query: IRoute.IParameter | undefined;
43
- input: IRoute.IParameter | undefined;
44
- }): string => {
45
- const encrypted: boolean =
46
- route.encrypted === true ||
47
- (props.input !== undefined &&
48
- props.input.custom === true &&
49
- props.input.category === "body" &&
50
- props.input.encrypted === true);
51
-
52
- // FETCH ARGUMENTS WITH REQUST BODY
53
- const parameters: IRoute.IParameter[] = filter_path_parameters(route)(
54
- props.query,
55
- );
56
- const contentType: string | undefined =
57
- props.input !== undefined
58
- ? (props.input as IController.IBodyParameter).encrypted
59
- ? "text/plain"
60
- : (props.input as IController.IBodyParameter).contentType ??
61
- "application/json"
62
- : undefined;
63
- const fetchArguments: Array<string | string[]> = [
64
- contentType
65
- ? [
66
- "{",
67
- " ...connection,",
68
- " headers: {",
69
- " ...(connection.headers ?? {}),",
70
- ` "Content-Type": "${contentType}",`,
71
- " },",
72
- "}",
73
- ]
74
- : "connection",
75
- [
76
- "{",
77
- ` ...${route.name}.METADATA,`,
78
- ` path: ${route.name}.path(${parameters
79
- .map((p) => p.name)
80
- .join(", ")}),`,
81
- "} as const",
82
- ],
83
- ];
84
- if (props.input !== undefined) {
85
- fetchArguments.push(props.input.name);
86
- if (config.json === true)
87
- fetchArguments.push(`${route.name}.stringify`);
88
- }
89
-
90
- const assertions: string =
91
- config.assert === true &&
92
- route.parameters.filter(
93
- (p) => p.category !== "headers" || p.field === undefined,
94
- ).length !== 0
95
- ? route.parameters
96
- .filter((p) => p.category !== "headers" || p.field === undefined)
97
- .map(
98
- (param) =>
99
- ` ${SdkImportWizard.typia(importer)}.assert<typeof ${
100
- param.name
101
- }>(${param.name});`,
102
- )
103
- .join("\n") + "\n\n"
104
- : "";
105
-
106
- // FUNCTION CALL STATEMENT
107
- const caller = (awa: boolean) => {
108
- const random = () =>
109
- [
110
- `${awa ? "await " : ""}${route.name}.simulate(`,
111
- ` connection,`,
112
- ...route.parameters
113
- .filter((p) => p.category !== "headers")
114
- .map((p) => ` ${p.name},`),
115
- `)`,
116
- ]
117
- .map((line, i) => (i === 0 ? line : `${space(10)}${line}`))
118
- .join("\n");
119
- const fetch = (tab: string) =>
120
- [
121
- `${awa ? "await " : ""}${SdkImportWizard.Fetcher(encrypted)(
122
- importer,
123
- )}.${config.propagate === true ? "propagate" : "fetch"}(`,
124
- fetchArguments
125
- .map((param) =>
126
- typeof param === "string"
127
- ? `${tab} ${param}`
128
- : param.map((str) => `${tab} ${str}`).join("\n"),
129
- )
130
- .join(",\n") + ",",
131
- `${tab})`,
132
- ].join("\n");
133
- if (!config.simulate) return fetch(space(4));
134
- return (
135
- `!!connection.simulate\n` +
136
- ` ? ${random()}\n` +
137
- ` : ${fetch(space(10))}`
138
- );
139
- };
140
- if (route.setHeaders.length === 0)
141
- return `{\n${assertions} return ${caller(false)};\n}`;
142
-
143
- // SET HEADERS
144
- const content: string[] = [
145
- `{\n`,
146
- assertions,
147
- ` const output: ${route.name}.Output = ${caller(true)};\n`,
148
- "\n",
149
- ` // configure header(s)\n`,
150
- ` connection.headers ??= {};\n`,
151
- ];
152
- const headerContents = (variable: string) =>
153
- route.setHeaders.map((header) =>
154
- header.type === "assigner"
155
- ? `Object.assign(connection.headers, ${access(variable)(
156
- header.source,
157
- )});`
158
- : `${access("connection.headers")(
159
- header.target ?? header.source,
160
- )} = ${access(variable)(header.source)};`,
161
- );
162
- if (config.propagate === true) {
163
- content.push(` if (output.success) {\n`);
164
- content.push(
165
- ...headerContents("output.data").map((line) => ` ${line}\n`),
166
- );
167
- content.push(` }\n`);
168
- } else
169
- content.push(
170
- ...headerContents("output").map((line) => ` ${line}\n`),
171
- );
172
- content.push("\n", " return output;\n", "}");
173
- return content.join("");
174
- };
175
-
176
- const filter_path_parameters =
177
- (route: IRoute) =>
178
- (query: IRoute.IParameter | undefined): IRoute.IParameter[] => {
179
- const parameters: IRoute.IParameter[] = route.parameters.filter(
180
- (param) =>
181
- param.category === "param" ||
182
- (param.category === "query" && param.field !== undefined),
183
- );
184
- if (query) parameters.push(query);
185
- return parameters;
186
- };
187
-
188
- const access =
189
- (x: string) =>
190
- (y: string): string =>
191
- y[0] === "[" ? `${x}${y}` : `${x}.${y}`;
192
-
193
- /* ---------------------------------------------------------
194
- HEAD & TAIL
195
- --------------------------------------------------------- */
196
- const head =
197
- (config: INestiaConfig) =>
198
- (importer: ImportDictionary) =>
199
- (route: IRoute) =>
200
- (props: {
201
- query: IRoute.IParameter | undefined;
202
- input: IRoute.IParameter | undefined;
203
- }): string => {
204
- //----
205
- // CONSTRUCT COMMENT
206
- //----
207
- // MAIN DESCRIPTION
208
- const comments: string[] = route.description
209
- ? route.description.split("\n")
210
- : [];
211
-
212
- // COMMENT TAGS
213
- const tags: IJsDocTagInfo[] = route.jsDocTags.filter(
214
- (tag) =>
215
- tag.name !== "param" ||
216
- route.parameters
217
- .filter((p) => p.category !== "headers")
218
- .some((p) => p.name === tag.text?.[0]?.text),
219
- );
220
- if (tags.length !== 0) {
221
- const content: string[] = tags.map((t) =>
222
- t.text?.length
223
- ? `@${t.name} ${t.text.map((e) => e.text).join("")}`
224
- : `@${t.name}`,
225
- );
226
- comments.push("", ...new Set(content));
227
- }
228
-
229
- // EXCEPTIONS
230
- for (const [key, value] of Object.entries(route.exceptions)) {
231
- if (
232
- comments.some(
233
- (str) =>
234
- str.startsWith(`@throw ${key}`) ||
235
- str.startsWith(`@throws ${key}`),
236
- )
237
- )
238
- continue;
239
- comments.push(
240
- value.description?.length
241
- ? `@throws ${key} ${value.description.split("\n")[0]}`
242
- : `@throws ${key}`,
243
- );
244
- }
245
-
246
- // POSTFIX
247
- if (!!comments.length) comments.push("");
248
- comments.push(
249
- `@controller ${route.symbol.class}.${route.symbol.function}`,
250
- `@path ${route.method} ${route.path}`,
251
- `@nestia Generated by Nestia - https://github.com/samchon/nestia`,
252
- );
253
-
254
- //----
255
- // FINALIZATION
256
- //----
257
- // REFORM PARAMETERS TEXT
258
- const parameters: string[] = [
259
- route.parameters.some(
260
- (p) => p.category === "headers" && p.field === undefined,
261
- )
262
- ? `connection: ${SdkImportWizard.IConnection(
263
- importer,
264
- )}<${`${route.name}.Headers`}>`
265
- : `connection: ${SdkImportWizard.IConnection(importer)}`,
266
- ...route.parameters
267
- .filter((p) => p.category !== "headers")
268
- .map((param) => {
269
- const type: string =
270
- config.primitive !== false &&
271
- (param === props.query || param === props.input)
272
- ? `${route.name}.${param === props.query ? "Query" : "Input"}`
273
- : getTypeName(config)(importer)(param);
274
- return `${param.name}${param.optional ? "?" : ""}: ${type}`;
275
- }),
276
- ];
277
-
278
- // OUTPUT TYPE
279
- const output: string =
280
- config.propagate !== true && route.output.typeName === "void"
281
- ? "void"
282
- : `${route.name}.Output`;
283
-
284
- // RETURNS WITH CONSTRUCTION
285
- return (
286
- "" +
287
- "/**\n" +
288
- comments.map((str) => ` * ${str}`).join("\n") +
289
- "\n" +
290
- " */\n" +
291
- `export async function ${route.name}(\n` +
292
- parameters.map((str) => ` ${str},\n`).join("") +
293
- `): Promise<${output}>`
294
- );
295
- };
296
-
297
- const tail =
298
- (config: INestiaConfig) =>
299
- (importer: ImportDictionary) =>
300
- (route: IRoute) =>
301
- (props: {
302
- query: IRoute.IParameter | undefined;
303
- headers: IRoute.IParameter | undefined;
304
- input: IRoute.IParameter | undefined;
305
- }): string => {
306
- // LIST UP TYPES
307
- const types: Pair<string, string>[] = [];
308
- if (props.headers !== undefined)
309
- types.push(
310
- new Pair(
311
- "Headers",
312
- SdkTypeDefiner.headers(config)(importer)(props.headers),
313
- ),
314
- );
315
- if (props.query !== undefined)
316
- types.push(
317
- new Pair(
318
- "Query",
319
- SdkTypeDefiner.query(config)(importer)(props.query),
320
- ),
321
- );
322
- if (props.input !== undefined)
323
- types.push(
324
- new Pair(
325
- "Input",
326
- SdkTypeDefiner.input(config)(importer)(props.input),
327
- ),
328
- );
329
- if (config.propagate === true || route.output.typeName !== "void")
330
- types.push(
331
- new Pair("Output", SdkTypeDefiner.output(config)(importer)(route)),
332
- );
333
-
334
- // PATH WITH PARAMETERS
335
- const parameters: IRoute.IParameter[] = filter_path_parameters(route)(
336
- props.query,
337
- );
338
- const path: string = compute_path({
339
- path: route.path,
340
- query: props.query,
341
- parameters,
342
- });
343
- return (
344
- `export namespace ${route.name} {\n` +
345
- (types.length !== 0
346
- ? types
347
- .map(
348
- (tuple) => ` export type ${tuple.first} = ${tuple.second};`,
349
- )
350
- .join("\n") + "\n"
351
- : "") +
352
- "\n" +
353
- [
354
- "export const METADATA = {",
355
- ` method: "${route.method}",`,
356
- ` path: "${route.path}",`,
357
- ...(props.input
358
- ? [
359
- `request: {`,
360
- ` type: "${
361
- (props.input as IController.IBodyParameter).encrypted
362
- ? "text/plain"
363
- : (props.input as IController.IBodyParameter).contentType ??
364
- "application/json"
365
- }",`,
366
- ` encrypted: ${
367
- props.input.custom &&
368
- props.input.category === "body" &&
369
- props.input.encrypted
370
- }`,
371
- `},`,
372
- ].map((str) => ` ${str}`)
373
- : [" request: null,"]),
374
- ...(route.method !== "HEAD"
375
- ? [
376
- `response: {`,
377
- ` type: "${route.output.contentType}",`,
378
- ` encrypted: ${route.encrypted},`,
379
- `},`,
380
- ].map((str) => ` ${str}`)
381
- : [" response: null,"]),
382
- ...(route.status
383
- ? [` status: ${route.status},`]
384
- : [" status: null,"]),
385
- ...(route.output.contentType === "application/x-www-form-urlencoded"
386
- ? [
387
- ` parseQuery: (input: URLSearchParams) => ${SdkImportWizard.typia(
388
- importer,
389
- )}.http.assertQuery<${route.output.typeName}>(input),`,
390
- ]
391
- : []),
392
- "} as const;",
393
- ]
394
- .map((line) => ` ${line}`)
395
- .join("\n") +
396
- "\n\n" +
397
- ` export const path = (${parameters
398
- .map(
399
- (param) =>
400
- `${param.name}: ${
401
- param.category === "query" &&
402
- param.typeName === props.query?.typeName
403
- ? `${route.name}.Query`
404
- : getTypeName(config)(importer)(param)
405
- }`,
406
- )
407
- .join(", ")}): string => {\n` +
408
- `${path};\n` +
409
- ` }\n` +
410
- (config.simulate === true && route.output.typeName !== "void"
411
- ? ` export const random = (g?: Partial<${SdkImportWizard.typia(
412
- importer,
413
- )}.IRandomGenerator>): ${SdkTypeDefiner.responseBody(config)(
414
- importer,
415
- )(route)} =>\n` +
416
- ` ${SdkImportWizard.typia(
417
- importer,
418
- )}.random<${SdkTypeDefiner.responseBody(config)(importer)(
419
- route,
420
- )}>(g);\n`
421
- : "") +
422
- (config.simulate === true
423
- ? SdkSimulationProgrammer.generate(config)(importer)(route) + "\n"
424
- : "") +
425
- (config.json === true &&
426
- route.parameters.find((param) => param.category === "body") !==
427
- undefined
428
- ? ` export const stringify = (input: Input) => ${SdkImportWizard.typia(
429
- importer,
430
- )}.json.assertStringify(input);\n`
431
- : "") +
432
- "}"
433
- );
434
- };
435
-
436
- const compute_path = (props: {
437
- query: IRoute.IParameter | undefined;
438
- parameters: IRoute.IParameter[];
439
- path: string;
440
- }): string => {
441
- for (const param of props.parameters)
442
- if (param.category === "param")
443
- props.path = props.path.replace(
444
- `:${param.field}`,
445
- `\${encodeURIComponent(${param.name} ?? "null")}`,
446
- );
447
-
448
- // NO QUERY PARAMETER
449
- const queryParams: IRoute.IParameter[] = props.parameters.filter(
450
- (param) => param.category === "query" && param.field !== undefined,
451
- );
452
- if (props.query === undefined && queryParams.length === 0)
453
- return `${space(8)}return \`${props.path}\``;
454
-
455
- const computeName = (str: string): string =>
456
- props.parameters
457
- .filter((p) => p.category !== "headers")
458
- .find((p) => p.name === str) !== undefined
459
- ? computeName("_" + str)
460
- : str;
461
- const variables: string = computeName("variables");
462
- const search: string = computeName("search");
463
- const encoded: string = computeName("encoded");
464
-
465
- const wrapper = (expr: string) =>
466
- [
467
- `const ${variables}: Record<any, any> = ${expr};`,
468
- `const ${search}: URLSearchParams = new URLSearchParams();`,
469
- `for (const [key, value] of Object.entries(${variables}))`,
470
- ` if (value === undefined) continue;`,
471
- ` else if (Array.isArray(value))`,
472
- ` value.forEach((elem) => ${search}.append(key, String(elem)));`,
473
- ` else`,
474
- ` ${search}.set(key, String(value));`,
475
- `const ${encoded}: string = ${search}.toString();`,
476
- `return \`${props.path}\${${encoded}.length ? \`?\${${encoded}}\` : ""}\`;`,
477
- ]
478
- .map((str) => `${space(8)}${str}`)
479
- .join("\n");
480
-
481
- if (props.query !== undefined && queryParams.length === 0)
482
- return wrapper(`${props.query.name} as any`);
483
- else if (props.query === undefined)
484
- return wrapper(`
485
- {
486
- ${rest_query_parameters(queryParams)}
487
- } as any`);
488
-
489
- return wrapper(`
490
- {
491
- ...${props.query.name},
492
- ${rest_query_parameters(queryParams)},
493
- } as any`);
494
- };
495
-
496
- const rest_query_parameters = (parameters: IRoute.IParameter[]): string =>
497
- parameters
498
- .filter((param) => param.category !== "headers")
499
- .map((param) =>
500
- param.name === param.field
501
- ? param.name
502
- : `${
503
- Escaper.variable(param.field!)
504
- ? param.field
505
- : JSON.stringify(param.field)
506
- }: ${param.name}`,
507
- )
508
- .join(`,\n${space(12)}`);
509
- }
510
-
511
- const space = (count: number) => " ".repeat(count);
512
- const getTypeName =
513
- (config: INestiaConfig) =>
514
- (importer: ImportDictionary) =>
515
- (p: IRoute.IParameter | IRoute.IOutput) =>
516
- p.metadata
517
- ? SdkDtoGenerator.decode(config)(importer)(p.metadata)
518
- : p.typeName;
1
+ import { Pair } from "tstl/utility/Pair";
2
+ import { IJsDocTagInfo } from "typia/lib/schemas/metadata/IJsDocTagInfo";
3
+ import { Escaper } from "typia/lib/utils/Escaper";
4
+
5
+ import { INestiaConfig } from "../../INestiaConfig";
6
+ import { IController } from "../../structures/IController";
7
+ import { IRoute } from "../../structures/IRoute";
8
+ import { ImportDictionary } from "../../utils/ImportDictionary";
9
+ import { SdkDtoGenerator } from "./SdkDtoGenerator";
10
+ import { SdkImportWizard } from "./SdkImportWizard";
11
+ import { SdkSimulationProgrammer } from "./SdkSimulationProgrammer";
12
+ import { SdkTypeDefiner } from "./SdkTypeDefiner";
13
+
14
+ export namespace SdkFunctionProgrammer {
15
+ export const generate =
16
+ (config: INestiaConfig) =>
17
+ (importer: ImportDictionary) =>
18
+ (route: IRoute): string => {
19
+ const [x, y, z] = [head, body, tail].map((closure) =>
20
+ closure(config)(importer)(route)({
21
+ headers: route.parameters.find(
22
+ (param) =>
23
+ param.category === "headers" && param.field === undefined,
24
+ ),
25
+ query: route.parameters.find(
26
+ (param) => param.category === "query" && param.field === undefined,
27
+ ),
28
+ input: route.parameters.find((param) => param.category === "body"),
29
+ }),
30
+ );
31
+ return `${x} ${y}\n${z}`;
32
+ };
33
+
34
+ /* ---------------------------------------------------------
35
+ BODY
36
+ --------------------------------------------------------- */
37
+ const body =
38
+ (config: INestiaConfig) =>
39
+ (importer: ImportDictionary) =>
40
+ (route: IRoute) =>
41
+ (props: {
42
+ query: IRoute.IParameter | undefined;
43
+ input: IRoute.IParameter | undefined;
44
+ }): string => {
45
+ const encrypted: boolean =
46
+ route.encrypted === true ||
47
+ (props.input !== undefined &&
48
+ props.input.custom === true &&
49
+ props.input.category === "body" &&
50
+ props.input.encrypted === true);
51
+
52
+ // FETCH ARGUMENTS WITH REQUST BODY
53
+ const parameters: IRoute.IParameter[] = filter_path_parameters(route)(
54
+ props.query,
55
+ );
56
+ const contentType: string | undefined =
57
+ props.input !== undefined
58
+ ? (props.input as IController.IBodyParameter).encrypted
59
+ ? "text/plain"
60
+ : (props.input as IController.IBodyParameter).contentType ??
61
+ "application/json"
62
+ : undefined;
63
+ const fetchArguments: Array<string | string[]> = [
64
+ contentType
65
+ ? [
66
+ "{",
67
+ " ...connection,",
68
+ " headers: {",
69
+ " ...(connection.headers ?? {}),",
70
+ ` "Content-Type": "${contentType}",`,
71
+ " },",
72
+ "}",
73
+ ]
74
+ : "connection",
75
+ [
76
+ "{",
77
+ ` ...${route.name}.METADATA,`,
78
+ ` path: ${route.name}.path(${parameters
79
+ .map((p) => p.name)
80
+ .join(", ")}),`,
81
+ "} as const",
82
+ ],
83
+ ];
84
+ if (props.input !== undefined) {
85
+ fetchArguments.push(props.input.name);
86
+ if (config.json === true)
87
+ fetchArguments.push(`${route.name}.stringify`);
88
+ }
89
+
90
+ const assertions: string =
91
+ config.assert === true &&
92
+ route.parameters.filter(
93
+ (p) => p.category !== "headers" || p.field === undefined,
94
+ ).length !== 0
95
+ ? route.parameters
96
+ .filter((p) => p.category !== "headers" || p.field === undefined)
97
+ .map(
98
+ (param) =>
99
+ ` ${SdkImportWizard.typia(importer)}.assert<typeof ${
100
+ param.name
101
+ }>(${param.name});`,
102
+ )
103
+ .join("\n") + "\n\n"
104
+ : "";
105
+
106
+ // FUNCTION CALL STATEMENT
107
+ const caller = (awa: boolean) => {
108
+ const random = () =>
109
+ [
110
+ `${awa ? "await " : ""}${route.name}.simulate(`,
111
+ ` connection,`,
112
+ ...route.parameters
113
+ .filter((p) => p.category !== "headers")
114
+ .map((p) => ` ${p.name},`),
115
+ `)`,
116
+ ]
117
+ .map((line, i) => (i === 0 ? line : `${space(10)}${line}`))
118
+ .join("\n");
119
+ const fetch = (tab: string) =>
120
+ [
121
+ `${awa ? "await " : ""}${SdkImportWizard.Fetcher(encrypted)(
122
+ importer,
123
+ )}.${config.propagate === true ? "propagate" : "fetch"}(`,
124
+ fetchArguments
125
+ .map((param) =>
126
+ typeof param === "string"
127
+ ? `${tab} ${param}`
128
+ : param.map((str) => `${tab} ${str}`).join("\n"),
129
+ )
130
+ .join(",\n") + ",",
131
+ `${tab})`,
132
+ ].join("\n");
133
+ if (!config.simulate) return fetch(space(4));
134
+ return (
135
+ `!!connection.simulate\n` +
136
+ ` ? ${random()}\n` +
137
+ ` : ${fetch(space(10))}`
138
+ );
139
+ };
140
+ if (route.setHeaders.length === 0)
141
+ return `{\n${assertions} return ${caller(false)};\n}`;
142
+
143
+ // SET HEADERS
144
+ const content: string[] = [
145
+ `{\n`,
146
+ assertions,
147
+ ` const output: ${route.name}.Output = ${caller(true)};\n`,
148
+ "\n",
149
+ ` // configure header(s)\n`,
150
+ ` connection.headers ??= {};\n`,
151
+ ];
152
+ const headerContents = (variable: string) =>
153
+ route.setHeaders.map((header) =>
154
+ header.type === "assigner"
155
+ ? `Object.assign(connection.headers, ${access(variable)(
156
+ header.source,
157
+ )});`
158
+ : `${access("connection.headers")(
159
+ header.target ?? header.source,
160
+ )} = ${access(variable)(header.source)};`,
161
+ );
162
+ if (config.propagate === true) {
163
+ content.push(` if (output.success) {\n`);
164
+ content.push(
165
+ ...headerContents("output.data").map((line) => ` ${line}\n`),
166
+ );
167
+ content.push(` }\n`);
168
+ } else
169
+ content.push(
170
+ ...headerContents("output").map((line) => ` ${line}\n`),
171
+ );
172
+ content.push("\n", " return output;\n", "}");
173
+ return content.join("");
174
+ };
175
+
176
+ const filter_path_parameters =
177
+ (route: IRoute) =>
178
+ (query: IRoute.IParameter | undefined): IRoute.IParameter[] => {
179
+ const parameters: IRoute.IParameter[] = route.parameters.filter(
180
+ (param) =>
181
+ param.category === "param" ||
182
+ (param.category === "query" && param.field !== undefined),
183
+ );
184
+ if (query) parameters.push(query);
185
+ return parameters;
186
+ };
187
+
188
+ const access =
189
+ (x: string) =>
190
+ (y: string): string =>
191
+ y[0] === "[" ? `${x}${y}` : `${x}.${y}`;
192
+
193
+ /* ---------------------------------------------------------
194
+ HEAD & TAIL
195
+ --------------------------------------------------------- */
196
+ const head =
197
+ (config: INestiaConfig) =>
198
+ (importer: ImportDictionary) =>
199
+ (route: IRoute) =>
200
+ (props: {
201
+ query: IRoute.IParameter | undefined;
202
+ input: IRoute.IParameter | undefined;
203
+ }): string => {
204
+ //----
205
+ // CONSTRUCT COMMENT
206
+ //----
207
+ // MAIN DESCRIPTION
208
+ const comments: string[] = route.description
209
+ ? route.description.split("\n")
210
+ : [];
211
+
212
+ // COMMENT TAGS
213
+ const tags: IJsDocTagInfo[] = route.jsDocTags.filter(
214
+ (tag) =>
215
+ tag.name !== "param" ||
216
+ route.parameters
217
+ .filter((p) => p.category !== "headers")
218
+ .some((p) => p.name === tag.text?.[0]?.text),
219
+ );
220
+ if (tags.length !== 0) {
221
+ const content: string[] = tags.map((t) =>
222
+ t.text?.length
223
+ ? `@${t.name} ${t.text.map((e) => e.text).join("")}`
224
+ : `@${t.name}`,
225
+ );
226
+ comments.push("", ...new Set(content));
227
+ }
228
+
229
+ // EXCEPTIONS
230
+ for (const [key, value] of Object.entries(route.exceptions)) {
231
+ if (
232
+ comments.some(
233
+ (str) =>
234
+ str.startsWith(`@throw ${key}`) ||
235
+ str.startsWith(`@throws ${key}`),
236
+ )
237
+ )
238
+ continue;
239
+ comments.push(
240
+ value.description?.length
241
+ ? `@throws ${key} ${value.description.split("\n")[0]}`
242
+ : `@throws ${key}`,
243
+ );
244
+ }
245
+
246
+ // POSTFIX
247
+ if (!!comments.length) comments.push("");
248
+ comments.push(
249
+ `@controller ${route.symbol.class}.${route.symbol.function}`,
250
+ `@path ${route.method} ${route.path}`,
251
+ `@nestia Generated by Nestia - https://github.com/samchon/nestia`,
252
+ );
253
+
254
+ //----
255
+ // FINALIZATION
256
+ //----
257
+ // REFORM PARAMETERS TEXT
258
+ const parameters: string[] = [
259
+ route.parameters.some(
260
+ (p) => p.category === "headers" && p.field === undefined,
261
+ )
262
+ ? `connection: ${SdkImportWizard.IConnection(
263
+ importer,
264
+ )}<${`${route.name}.Headers`}>`
265
+ : `connection: ${SdkImportWizard.IConnection(importer)}`,
266
+ ...route.parameters
267
+ .filter((p) => p.category !== "headers")
268
+ .map((param) => {
269
+ const type: string =
270
+ config.primitive !== false &&
271
+ (param === props.query || param === props.input)
272
+ ? `${route.name}.${param === props.query ? "Query" : "Input"}`
273
+ : getTypeName(config)(importer)(param);
274
+ return `${param.name}${param.optional ? "?" : ""}: ${type}`;
275
+ }),
276
+ ];
277
+
278
+ // OUTPUT TYPE
279
+ const output: string =
280
+ config.propagate !== true && route.output.typeName === "void"
281
+ ? "void"
282
+ : `${route.name}.Output`;
283
+
284
+ // RETURNS WITH CONSTRUCTION
285
+ return (
286
+ "" +
287
+ "/**\n" +
288
+ comments.map((str) => ` * ${str}`).join("\n") +
289
+ "\n" +
290
+ " */\n" +
291
+ `export async function ${route.name}(\n` +
292
+ parameters.map((str) => ` ${str},\n`).join("") +
293
+ `): Promise<${output}>`
294
+ );
295
+ };
296
+
297
+ const tail =
298
+ (config: INestiaConfig) =>
299
+ (importer: ImportDictionary) =>
300
+ (route: IRoute) =>
301
+ (props: {
302
+ query: IRoute.IParameter | undefined;
303
+ headers: IRoute.IParameter | undefined;
304
+ input: IRoute.IParameter | undefined;
305
+ }): string => {
306
+ // LIST UP TYPES
307
+ const types: Pair<string, string>[] = [];
308
+ if (props.headers !== undefined)
309
+ types.push(
310
+ new Pair(
311
+ "Headers",
312
+ SdkTypeDefiner.headers(config)(importer)(props.headers),
313
+ ),
314
+ );
315
+ if (props.query !== undefined)
316
+ types.push(
317
+ new Pair(
318
+ "Query",
319
+ SdkTypeDefiner.query(config)(importer)(props.query),
320
+ ),
321
+ );
322
+ if (props.input !== undefined)
323
+ types.push(
324
+ new Pair(
325
+ "Input",
326
+ SdkTypeDefiner.input(config)(importer)(props.input),
327
+ ),
328
+ );
329
+ if (config.propagate === true || route.output.typeName !== "void")
330
+ types.push(
331
+ new Pair("Output", SdkTypeDefiner.output(config)(importer)(route)),
332
+ );
333
+
334
+ // PATH WITH PARAMETERS
335
+ const parameters: IRoute.IParameter[] = filter_path_parameters(route)(
336
+ props.query,
337
+ );
338
+ const path: string = compute_path({
339
+ path: route.path,
340
+ query: props.query,
341
+ parameters,
342
+ });
343
+ return (
344
+ `export namespace ${route.name} {\n` +
345
+ (types.length !== 0
346
+ ? types
347
+ .map(
348
+ (tuple) => ` export type ${tuple.first} = ${tuple.second};`,
349
+ )
350
+ .join("\n") + "\n"
351
+ : "") +
352
+ "\n" +
353
+ [
354
+ "export const METADATA = {",
355
+ ` method: "${route.method}",`,
356
+ ` path: "${route.path}",`,
357
+ ...(props.input
358
+ ? [
359
+ `request: {`,
360
+ ` type: "${
361
+ (props.input as IController.IBodyParameter).encrypted
362
+ ? "text/plain"
363
+ : (props.input as IController.IBodyParameter).contentType ??
364
+ "application/json"
365
+ }",`,
366
+ ` encrypted: ${
367
+ props.input.custom &&
368
+ props.input.category === "body" &&
369
+ props.input.encrypted
370
+ }`,
371
+ `},`,
372
+ ].map((str) => ` ${str}`)
373
+ : [" request: null,"]),
374
+ ...(route.method !== "HEAD"
375
+ ? [
376
+ `response: {`,
377
+ ` type: "${route.output.contentType}",`,
378
+ ` encrypted: ${route.encrypted},`,
379
+ `},`,
380
+ ].map((str) => ` ${str}`)
381
+ : [" response: null,"]),
382
+ ...(route.status
383
+ ? [` status: ${route.status},`]
384
+ : [" status: null,"]),
385
+ ...(route.output.contentType === "application/x-www-form-urlencoded"
386
+ ? [
387
+ ` parseQuery: (input: URLSearchParams) => ${SdkImportWizard.typia(
388
+ importer,
389
+ )}.http.assertQuery<${route.output.typeName}>(input),`,
390
+ ]
391
+ : []),
392
+ "} as const;",
393
+ ]
394
+ .map((line) => ` ${line}`)
395
+ .join("\n") +
396
+ "\n\n" +
397
+ ` export const path = (${parameters
398
+ .map(
399
+ (param) =>
400
+ `${param.name}: ${
401
+ param.category === "query" &&
402
+ param.typeName === props.query?.typeName
403
+ ? `${route.name}.Query`
404
+ : getTypeName(config)(importer)(param)
405
+ }`,
406
+ )
407
+ .join(", ")}): string => {\n` +
408
+ `${path};\n` +
409
+ ` }\n` +
410
+ (config.simulate === true && route.output.typeName !== "void"
411
+ ? ` export const random = (g?: Partial<${SdkImportWizard.typia(
412
+ importer,
413
+ )}.IRandomGenerator>): ${SdkTypeDefiner.responseBody(config)(
414
+ importer,
415
+ )(route)} =>\n` +
416
+ ` ${SdkImportWizard.typia(
417
+ importer,
418
+ )}.random<${SdkTypeDefiner.responseBody(config)(importer)(
419
+ route,
420
+ )}>(g);\n`
421
+ : "") +
422
+ (config.simulate === true
423
+ ? SdkSimulationProgrammer.generate(config)(importer)(route) + "\n"
424
+ : "") +
425
+ (config.json === true &&
426
+ route.parameters.find((param) => param.category === "body") !==
427
+ undefined
428
+ ? ` export const stringify = (input: Input) => ${SdkImportWizard.typia(
429
+ importer,
430
+ )}.json.assertStringify(input);\n`
431
+ : "") +
432
+ "}"
433
+ );
434
+ };
435
+
436
+ const compute_path = (props: {
437
+ query: IRoute.IParameter | undefined;
438
+ parameters: IRoute.IParameter[];
439
+ path: string;
440
+ }): string => {
441
+ for (const param of props.parameters)
442
+ if (param.category === "param")
443
+ props.path = props.path.replace(
444
+ `:${param.field}`,
445
+ `\${encodeURIComponent(${param.name} ?? "null")}`,
446
+ );
447
+
448
+ // NO QUERY PARAMETER
449
+ const queryParams: IRoute.IParameter[] = props.parameters.filter(
450
+ (param) => param.category === "query" && param.field !== undefined,
451
+ );
452
+ if (props.query === undefined && queryParams.length === 0)
453
+ return `${space(8)}return \`${props.path}\``;
454
+
455
+ const computeName = (str: string): string =>
456
+ props.parameters
457
+ .filter((p) => p.category !== "headers")
458
+ .find((p) => p.name === str) !== undefined
459
+ ? computeName("_" + str)
460
+ : str;
461
+ const variables: string = computeName("variables");
462
+ const search: string = computeName("search");
463
+ const encoded: string = computeName("encoded");
464
+
465
+ const wrapper = (expr: string) =>
466
+ [
467
+ `const ${variables}: Record<any, any> = ${expr};`,
468
+ `const ${search}: URLSearchParams = new URLSearchParams();`,
469
+ `for (const [key, value] of Object.entries(${variables}))`,
470
+ ` if (value === undefined) continue;`,
471
+ ` else if (Array.isArray(value))`,
472
+ ` value.forEach((elem) => ${search}.append(key, String(elem)));`,
473
+ ` else`,
474
+ ` ${search}.set(key, String(value));`,
475
+ `const ${encoded}: string = ${search}.toString();`,
476
+ `return \`${props.path}\${${encoded}.length ? \`?\${${encoded}}\` : ""}\`;`,
477
+ ]
478
+ .map((str) => `${space(8)}${str}`)
479
+ .join("\n");
480
+
481
+ if (props.query !== undefined && queryParams.length === 0)
482
+ return wrapper(`${props.query.name} as any`);
483
+ else if (props.query === undefined)
484
+ return wrapper(`
485
+ {
486
+ ${rest_query_parameters(queryParams)}
487
+ } as any`);
488
+
489
+ return wrapper(`
490
+ {
491
+ ...${props.query.name},
492
+ ${rest_query_parameters(queryParams)},
493
+ } as any`);
494
+ };
495
+
496
+ const rest_query_parameters = (parameters: IRoute.IParameter[]): string =>
497
+ parameters
498
+ .filter((param) => param.category !== "headers")
499
+ .map((param) =>
500
+ param.name === param.field
501
+ ? param.name
502
+ : `${
503
+ Escaper.variable(param.field!)
504
+ ? param.field
505
+ : JSON.stringify(param.field)
506
+ }: ${param.name}`,
507
+ )
508
+ .join(`,\n${space(12)}`);
509
+ }
510
+
511
+ const space = (count: number) => " ".repeat(count);
512
+ const getTypeName =
513
+ (config: INestiaConfig) =>
514
+ (importer: ImportDictionary) =>
515
+ (p: IRoute.IParameter | IRoute.IOutput) =>
516
+ p.metadata
517
+ ? SdkDtoGenerator.decode(config)(importer)(p.metadata)
518
+ : p.typeName;