@nestia/migrate 0.11.4 → 0.11.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 (52) hide show
  1. package/lib/bundles/NEST_TEMPLATE.js +5 -5
  2. package/lib/bundles/NEST_TEMPLATE.js.map +1 -1
  3. package/lib/bundles/SDK_TEMPLATE.js +1 -1
  4. package/lib/utils/openapi-down-convert/converter.js +2 -2
  5. package/package.json +2 -2
  6. package/src/MigrateApplication.ts +81 -81
  7. package/src/analyzers/MigrateAnalyzer.ts +9 -9
  8. package/src/analyzers/MigrateControllerAnalyzer.ts +135 -135
  9. package/src/analyzers/MigrateMethodAnalyzer.ts +439 -439
  10. package/src/archivers/MigrateFileArchiver.ts +38 -38
  11. package/src/bundles/NEST_TEMPLATE.ts +5 -5
  12. package/src/bundles/SDK_TEMPLATE.ts +1 -1
  13. package/src/executable/bundle.ts +110 -110
  14. package/src/internal/MigrateCommander.ts +70 -70
  15. package/src/internal/MigrateInquirer.ts +86 -86
  16. package/src/module.ts +14 -14
  17. package/src/programmers/MigrateApiFileProgrammer.ts +53 -53
  18. package/src/programmers/MigrateApiFunctionProgrammer.ts +199 -199
  19. package/src/programmers/MigrateApiNamespaceProgrammer.ts +431 -431
  20. package/src/programmers/MigrateApiProgrammer.ts +170 -170
  21. package/src/programmers/MigrateApiSimulatationProgrammer.ts +327 -327
  22. package/src/programmers/MigrateApiStartProgrammer.ts +194 -194
  23. package/src/programmers/MigrateDtoProgrammer.ts +78 -78
  24. package/src/programmers/MigrateE2eFileProgrammer.ts +117 -117
  25. package/src/programmers/MigrateE2eProgrammer.ts +36 -36
  26. package/src/programmers/MigrateImportProgrammer.ts +121 -121
  27. package/src/programmers/MigrateNestControllerProgrammer.ts +50 -50
  28. package/src/programmers/MigrateNestMethodProgrammer.ts +250 -250
  29. package/src/programmers/MigrateNestModuleProgrammer.ts +63 -63
  30. package/src/programmers/MigrateNestProgrammer.ts +74 -74
  31. package/src/programmers/MigrateSchemaProgrammer.ts +267 -267
  32. package/src/structures/IMigrateDto.ts +8 -8
  33. package/src/structures/IMigrateProgram.ts +27 -27
  34. package/src/structures/IMigrateRoute.ts +51 -51
  35. package/src/structures/ISwagger.ts +23 -23
  36. package/src/structures/ISwaggerComponents.ts +14 -14
  37. package/src/structures/ISwaggerRoute.ts +20 -20
  38. package/src/structures/ISwaggerRouteBodyContent.ts +15 -15
  39. package/src/structures/ISwaggerRouteParameter.ts +14 -14
  40. package/src/structures/ISwaggerRouteRequestBody.ts +12 -12
  41. package/src/structures/ISwaggerRouteResponse.ts +11 -11
  42. package/src/structures/ISwaggerSchema.ts +90 -90
  43. package/src/structures/ISwaggerSecurityScheme.ts +47 -47
  44. package/src/structures/ISwaggerV20.ts +10 -10
  45. package/src/structures/ISwaggerV31.ts +10 -10
  46. package/src/utils/FilePrinter.ts +36 -36
  47. package/src/utils/OpenApiConverter.ts +19 -19
  48. package/src/utils/StringUtil.ts +60 -60
  49. package/src/utils/SwaggerComponentsExplorer.ts +43 -43
  50. package/src/utils/SwaggerTypeChecker.ts +67 -67
  51. package/src/utils/openapi-down-convert/RefVisitor.ts +139 -139
  52. package/src/utils/openapi-down-convert/converter.ts +527 -527
@@ -1,439 +1,439 @@
1
- import typia from "typia";
2
- import { Escaper } from "typia/lib/utils/Escaper";
3
-
4
- import { IMigrateProgram } from "../structures/IMigrateProgram";
5
- import { IMigrateRoute } from "../structures/IMigrateRoute";
6
- import { ISwagger } from "../structures/ISwagger";
7
- import { ISwaggerRoute } from "../structures/ISwaggerRoute";
8
- import { ISwaggerRouteBodyContent } from "../structures/ISwaggerRouteBodyContent";
9
- import { ISwaggerRouteParameter } from "../structures/ISwaggerRouteParameter";
10
- import { ISwaggerRouteResponse } from "../structures/ISwaggerRouteResponse";
11
- import { ISwaggerSchema } from "../structures/ISwaggerSchema";
12
- import { StringUtil } from "../utils/StringUtil";
13
- import { SwaggerComponentsExplorer } from "../utils/SwaggerComponentsExplorer";
14
- import { SwaggerTypeChecker } from "../utils/SwaggerTypeChecker";
15
-
16
- export namespace MigrateMethodAnalzyer {
17
- export const analyze =
18
- (props: Omit<IMigrateProgram.IProps, "dictionary">) =>
19
- (endpoint: { path: string; method: string }) =>
20
- (route: ISwaggerRoute): IMigrateRoute | null => {
21
- const body = emplaceBodySchema("request")(
22
- emplaceReference(props.swagger)("body"),
23
- )(route.requestBody);
24
- const success = emplaceBodySchema("response")(
25
- emplaceReference(props.swagger)("response"),
26
- )(
27
- (() => {
28
- const response =
29
- route.responses?.["201"] ??
30
- route.responses?.["200"] ??
31
- route.responses?.default;
32
- if (response === undefined) return undefined;
33
- return (
34
- SwaggerComponentsExplorer.getResponse(props.swagger.components)(
35
- response,
36
- ) ?? undefined
37
- );
38
- })(),
39
- );
40
-
41
- const failures: string[] = [];
42
- for (const p of route.parameters ?? [])
43
- if (
44
- SwaggerComponentsExplorer.getParameter(props.swagger.components)(
45
- p,
46
- ) === null
47
- )
48
- failures.push(
49
- `parameter "${(p as ISwaggerRouteParameter.IReference).$ref}" is not defined in "components.parameters".`,
50
- );
51
- for (const value of Object.values(route.responses ?? {}))
52
- if (
53
- SwaggerComponentsExplorer.getResponse(props.swagger.components)(
54
- value,
55
- ) === null
56
- )
57
- failures.push(
58
- `response "${(value as ISwaggerRouteResponse.IReference).$ref}" is not defined in "components.responses".`,
59
- );
60
- if (
61
- route.requestBody &&
62
- SwaggerComponentsExplorer.getRequestBody(props.swagger.components)(
63
- route.requestBody,
64
- ) === null
65
- )
66
- failures.push(
67
- `requestBody "${(route.requestBody as ISwaggerRouteParameter.IReference).$ref}" is not defined in "components.requestBodies".`,
68
- );
69
- if (body === false)
70
- failures.push(
71
- `supports only "application/json", "application/x-www-form-urlencoded", "multipart/form-data" and "text/plain" content type in the request body.`,
72
- );
73
- if (success === false)
74
- failures.push(
75
- `supports only "application/json", "application/x-www-form-urlencoded" and "text/plain" content type in the response body.`,
76
- );
77
- if (SUPPORTED_METHODS.has(endpoint.method.toUpperCase()) === false)
78
- failures.push(
79
- `does not support ${endpoint.method.toUpperCase()} method.`,
80
- );
81
-
82
- const [headers, query] = ["header", "query"].map((type) => {
83
- const parameters: ISwaggerRouteParameter[] = (route.parameters ?? [])
84
- .filter(
85
- (p) =>
86
- SwaggerComponentsExplorer.getParameter(props.swagger.components)(
87
- p,
88
- )?.in === type,
89
- )
90
- .map(
91
- (p) =>
92
- SwaggerComponentsExplorer.getParameter(props.swagger.components)(
93
- p,
94
- )!,
95
- );
96
- if (parameters.length === 0) return null;
97
-
98
- const objects = parameters
99
- .map((p) =>
100
- SwaggerTypeChecker.isObject(p.schema)
101
- ? p.schema
102
- : SwaggerTypeChecker.isReference(p.schema) &&
103
- SwaggerTypeChecker.isObject(
104
- (props.swagger.components.schemas ?? {})[
105
- p.schema.$ref.replace(`#/components/schemas/`, ``)
106
- ] ?? {},
107
- )
108
- ? p.schema
109
- : null!,
110
- )
111
- .filter((s) => !!s);
112
- const primitives = parameters.filter(
113
- (p) =>
114
- SwaggerTypeChecker.isBoolean(p.schema) ||
115
- SwaggerTypeChecker.isNumber(p.schema) ||
116
- SwaggerTypeChecker.isInteger(p.schema) ||
117
- SwaggerTypeChecker.isString(p.schema) ||
118
- SwaggerTypeChecker.isArray(p.schema),
119
- );
120
- if (objects.length === 1 && primitives.length === 0) return objects[0];
121
- else if (objects.length > 1) {
122
- failures.push(
123
- `${type} typed parameters must be only one object type`,
124
- );
125
- return false;
126
- }
127
-
128
- const dto: ISwaggerSchema.IObject | null = objects[0]
129
- ? SwaggerTypeChecker.isObject(objects[0])
130
- ? objects[0]
131
- : ((props.swagger.components.schemas ?? {})[
132
- (objects[0] as ISwaggerSchema.IReference).$ref.replace(
133
- `#/components/schemas/`,
134
- ``,
135
- )
136
- ] as ISwaggerSchema.IObject)
137
- : null;
138
- const entire: ISwaggerSchema.IObject[] = [
139
- ...objects.map((o) =>
140
- SwaggerTypeChecker.isObject(o)
141
- ? o
142
- : (props.swagger.components.schemas?.[
143
- o.$ref.replace(`#/components/schemas/`, ``)
144
- ]! as ISwaggerSchema.IObject),
145
- ),
146
- {
147
- type: "object",
148
- properties: Object.fromEntries([
149
- ...primitives.map((p) => [
150
- p.name,
151
- {
152
- ...p.schema,
153
- description: p.schema.description ?? p.description,
154
- },
155
- ]),
156
- ...(dto ? Object.entries(dto.properties ?? {}) : []),
157
- ]),
158
- required: [
159
- ...primitives.filter((p) => p.required).map((p) => p.name!),
160
- ...(dto ? dto.required ?? [] : []),
161
- ],
162
- },
163
- ];
164
- return parameters.length === 0
165
- ? null
166
- : emplaceReference(props.swagger)(
167
- StringUtil.pascal(`I/Api/${endpoint.path}`) +
168
- "." +
169
- StringUtil.pascal(`${endpoint.method}/${type}`),
170
- )({
171
- type: "object",
172
- properties: Object.fromEntries([
173
- ...new Map<string, ISwaggerSchema>(
174
- entire
175
- .map((o) =>
176
- Object.entries(o.properties ?? {}).map(
177
- ([name, schema]) =>
178
- [
179
- name,
180
- {
181
- ...schema,
182
- description:
183
- schema.description ?? schema.description,
184
- } as ISwaggerSchema,
185
- ] as const,
186
- ),
187
- )
188
- .flat(),
189
- ),
190
- ]),
191
- required: [
192
- ...new Set(entire.map((o) => o.required ?? []).flat()),
193
- ],
194
- });
195
- });
196
-
197
- const parameterNames: string[] = StringUtil.splitWithNormalization(
198
- endpoint.path,
199
- )
200
- .filter((str) => str[0] === "{" || str[0] === ":")
201
- .map((str) =>
202
- str[0] === "{" ? str.substring(1, str.length - 1) : str.substring(1),
203
- );
204
- if (
205
- parameterNames.length !==
206
- (route.parameters ?? []).filter(
207
- (p) =>
208
- SwaggerComponentsExplorer.getParameter(props.swagger.components)(p)
209
- ?.in === "path",
210
- ).length
211
- )
212
- failures.push(
213
- "number of path parameters are not matched with its full path.",
214
- );
215
-
216
- if (failures.length) {
217
- console.log(
218
- `Failed to migrate ${endpoint.method.toUpperCase()} ${endpoint.path}`,
219
- ...failures.map((f) => ` - ${f}`),
220
- );
221
- return null;
222
- }
223
- return {
224
- name: "@lazy",
225
- originalPath: endpoint.path,
226
- path: endpoint.path,
227
- method: endpoint.method,
228
- accessor: ["@lazy"],
229
- headers: headers
230
- ? {
231
- name: "headers",
232
- key: "headers",
233
- schema: headers,
234
- }
235
- : null,
236
- parameters: (route.parameters ?? [])
237
- .map((p) =>
238
- SwaggerComponentsExplorer.getParameter(props.swagger.components)(p),
239
- )
240
- .filter((p) => p !== null && p.in === "path")
241
- .map((p, i) => ({
242
- name: parameterNames[i],
243
- key: (() => {
244
- let key: string = StringUtil.normalize(parameterNames[i]);
245
- if (Escaper.variable(key)) return key;
246
- while (true) {
247
- key = "_" + key;
248
- if (!parameterNames.some((s) => s === key)) return key;
249
- }
250
- })(),
251
- schema: {
252
- ...p!.schema,
253
- description: p!.schema.description ?? p!.description,
254
- },
255
- })),
256
- query: query
257
- ? {
258
- name: "query",
259
- key: "query",
260
- schema: query,
261
- }
262
- : null,
263
- body: body as IMigrateRoute.IBody | null,
264
- success: success as IMigrateRoute.IBody | null,
265
- exceptions: Object.fromEntries(
266
- Object.entries(route.responses ?? {})
267
- .filter(
268
- ([key, value]) =>
269
- key !== "200" &&
270
- key !== "201" &&
271
- key !== "default" &&
272
- !!SwaggerComponentsExplorer.getResponse(
273
- props.swagger.components,
274
- )(value)?.content?.["application/json"],
275
- )
276
- .map(([key, value]) => {
277
- const r = SwaggerComponentsExplorer.getResponse(
278
- props.swagger.components,
279
- )(value)!;
280
- return [
281
- key,
282
- {
283
- description: r.description,
284
- schema: r.content?.["application/json"]?.schema ?? {},
285
- },
286
- ];
287
- }),
288
- ),
289
- deprecated: route.deprecated ?? false,
290
- comment: () => describe(props.swagger)(route),
291
- tags: route.tags ?? [],
292
- };
293
- };
294
-
295
- const describe =
296
- (swagger: ISwagger) =>
297
- (route: ISwaggerRoute): string => {
298
- const commentTags: string[] = [];
299
- const add = (text: string) => {
300
- if (commentTags.every((line) => line !== text)) commentTags.push(text);
301
- };
302
-
303
- let description: string = route.description ?? "";
304
- if (route.summary) {
305
- const emended: string = route.summary.endsWith(".")
306
- ? route.summary
307
- : route.summary + ".";
308
- if (
309
- !!description.length &&
310
- !description.startsWith(route.summary) &&
311
- !route["x-nestia-jsDocTags"]?.some((t) => t.name === "summary")
312
- )
313
- description = `${emended}\n${description}`;
314
- }
315
- for (const p of route.parameters ?? []) {
316
- const param: ISwaggerRouteParameter | null = (() => {
317
- if (!typia.is<ISwaggerRouteParameter.IReference>(p))
318
- return typia.is<ISwaggerRouteParameter>(p) ? p : null;
319
- return (
320
- swagger.components.parameters?.[
321
- p.$ref.replace(`#/components/parameters/`, ``)
322
- ] ?? null
323
- );
324
- })();
325
- if (param !== null && param.description)
326
- add(`@param ${param.name} ${param.description}`);
327
- }
328
- if (route.requestBody?.description)
329
- add(`@param body ${route.requestBody.description}`);
330
- for (const security of route.security ?? [])
331
- for (const [name, scopes] of Object.entries(security))
332
- add(`@security ${[name, ...scopes].join("")}`);
333
- if (route.tags) route.tags.forEach((name) => add(`@tag ${name}`));
334
- if (route.deprecated) add("@deprecated");
335
- return description.length
336
- ? commentTags.length
337
- ? `${description}\n\n${commentTags.join("\n")}`
338
- : description
339
- : commentTags.join("\n");
340
- };
341
-
342
- const isNotObjectLiteral = (schema: ISwaggerSchema): boolean =>
343
- SwaggerTypeChecker.isReference(schema) ||
344
- SwaggerTypeChecker.isBoolean(schema) ||
345
- SwaggerTypeChecker.isNumber(schema) ||
346
- SwaggerTypeChecker.isString(schema) ||
347
- SwaggerTypeChecker.isUnknown(schema) ||
348
- (SwaggerTypeChecker.isAnyOf(schema) &&
349
- schema.anyOf.every(isNotObjectLiteral)) ||
350
- (SwaggerTypeChecker.isOneOf(schema) &&
351
- schema.oneOf.every(isNotObjectLiteral)) ||
352
- (SwaggerTypeChecker.isArray(schema) && isNotObjectLiteral(schema.items));
353
-
354
- const emplaceBodySchema =
355
- (from: "request" | "response") =>
356
- (emplacer: (schema: ISwaggerSchema) => ISwaggerSchema.IReference) =>
357
- (meta?: {
358
- description?: string;
359
- content?: ISwaggerRouteBodyContent;
360
- "x-nestia-encrypted"?: boolean;
361
- }): false | null | IMigrateRoute.IBody => {
362
- if (!meta?.content) return null;
363
-
364
- const entries: [string, { schema: ISwaggerSchema }][] = Object.entries(
365
- meta.content,
366
- );
367
- const json = entries.find((e) =>
368
- meta["x-nestia-encrypted"] === true
369
- ? e[0].includes("text/plain") || e[0].includes("application/json")
370
- : e[0].includes("application/json") || e[0].includes("*/*"),
371
- );
372
- if (json) {
373
- const { schema } = json[1];
374
- return {
375
- type: "application/json",
376
- name: "body",
377
- key: "body",
378
- schema: isNotObjectLiteral(schema) ? schema : emplacer(schema),
379
- "x-nestia-encrypted": meta["x-nestia-encrypted"],
380
- };
381
- }
382
-
383
- const query = entries.find((e) =>
384
- e[0].includes("application/x-www-form-urlencoded"),
385
- );
386
- if (query) {
387
- const { schema } = query[1];
388
- return {
389
- type: "application/x-www-form-urlencoded",
390
- name: "body",
391
- key: "body",
392
- schema: isNotObjectLiteral(schema) ? schema : emplacer(schema),
393
- };
394
- }
395
-
396
- const text = entries.find((e) => e[0].includes("text/plain"));
397
- if (text)
398
- return {
399
- type: "text/plain",
400
- name: "body",
401
- key: "body",
402
- schema: { type: "string" },
403
- };
404
-
405
- if (from === "request") {
406
- const multipart = entries.find((e) =>
407
- e[0].includes("multipart/form-data"),
408
- );
409
- if (multipart) {
410
- const { schema } = multipart[1];
411
- return {
412
- type: "multipart/form-data",
413
- name: "body",
414
- key: "body",
415
- schema: isNotObjectLiteral(schema) ? schema : emplacer(schema),
416
- };
417
- }
418
- }
419
- return false;
420
- };
421
-
422
- const emplaceReference =
423
- (swagger: ISwagger) =>
424
- (name: string) =>
425
- (schema: ISwaggerSchema): ISwaggerSchema.IReference => {
426
- swagger.components.schemas ??= {};
427
- swagger.components.schemas[name] = schema;
428
- return { $ref: `#/components/schemas/${name}` };
429
- };
430
- }
431
-
432
- const SUPPORTED_METHODS: Set<string> = new Set([
433
- "GET",
434
- "POST",
435
- "PUT",
436
- "PATCH",
437
- "DELETE",
438
- "HEAD",
439
- ]);
1
+ import typia from "typia";
2
+ import { Escaper } from "typia/lib/utils/Escaper";
3
+
4
+ import { IMigrateProgram } from "../structures/IMigrateProgram";
5
+ import { IMigrateRoute } from "../structures/IMigrateRoute";
6
+ import { ISwagger } from "../structures/ISwagger";
7
+ import { ISwaggerRoute } from "../structures/ISwaggerRoute";
8
+ import { ISwaggerRouteBodyContent } from "../structures/ISwaggerRouteBodyContent";
9
+ import { ISwaggerRouteParameter } from "../structures/ISwaggerRouteParameter";
10
+ import { ISwaggerRouteResponse } from "../structures/ISwaggerRouteResponse";
11
+ import { ISwaggerSchema } from "../structures/ISwaggerSchema";
12
+ import { StringUtil } from "../utils/StringUtil";
13
+ import { SwaggerComponentsExplorer } from "../utils/SwaggerComponentsExplorer";
14
+ import { SwaggerTypeChecker } from "../utils/SwaggerTypeChecker";
15
+
16
+ export namespace MigrateMethodAnalzyer {
17
+ export const analyze =
18
+ (props: Omit<IMigrateProgram.IProps, "dictionary">) =>
19
+ (endpoint: { path: string; method: string }) =>
20
+ (route: ISwaggerRoute): IMigrateRoute | null => {
21
+ const body = emplaceBodySchema("request")(
22
+ emplaceReference(props.swagger)("body"),
23
+ )(route.requestBody);
24
+ const success = emplaceBodySchema("response")(
25
+ emplaceReference(props.swagger)("response"),
26
+ )(
27
+ (() => {
28
+ const response =
29
+ route.responses?.["201"] ??
30
+ route.responses?.["200"] ??
31
+ route.responses?.default;
32
+ if (response === undefined) return undefined;
33
+ return (
34
+ SwaggerComponentsExplorer.getResponse(props.swagger.components)(
35
+ response,
36
+ ) ?? undefined
37
+ );
38
+ })(),
39
+ );
40
+
41
+ const failures: string[] = [];
42
+ for (const p of route.parameters ?? [])
43
+ if (
44
+ SwaggerComponentsExplorer.getParameter(props.swagger.components)(
45
+ p,
46
+ ) === null
47
+ )
48
+ failures.push(
49
+ `parameter "${(p as ISwaggerRouteParameter.IReference).$ref}" is not defined in "components.parameters".`,
50
+ );
51
+ for (const value of Object.values(route.responses ?? {}))
52
+ if (
53
+ SwaggerComponentsExplorer.getResponse(props.swagger.components)(
54
+ value,
55
+ ) === null
56
+ )
57
+ failures.push(
58
+ `response "${(value as ISwaggerRouteResponse.IReference).$ref}" is not defined in "components.responses".`,
59
+ );
60
+ if (
61
+ route.requestBody &&
62
+ SwaggerComponentsExplorer.getRequestBody(props.swagger.components)(
63
+ route.requestBody,
64
+ ) === null
65
+ )
66
+ failures.push(
67
+ `requestBody "${(route.requestBody as ISwaggerRouteParameter.IReference).$ref}" is not defined in "components.requestBodies".`,
68
+ );
69
+ if (body === false)
70
+ failures.push(
71
+ `supports only "application/json", "application/x-www-form-urlencoded", "multipart/form-data" and "text/plain" content type in the request body.`,
72
+ );
73
+ if (success === false)
74
+ failures.push(
75
+ `supports only "application/json", "application/x-www-form-urlencoded" and "text/plain" content type in the response body.`,
76
+ );
77
+ if (SUPPORTED_METHODS.has(endpoint.method.toUpperCase()) === false)
78
+ failures.push(
79
+ `does not support ${endpoint.method.toUpperCase()} method.`,
80
+ );
81
+
82
+ const [headers, query] = ["header", "query"].map((type) => {
83
+ const parameters: ISwaggerRouteParameter[] = (route.parameters ?? [])
84
+ .filter(
85
+ (p) =>
86
+ SwaggerComponentsExplorer.getParameter(props.swagger.components)(
87
+ p,
88
+ )?.in === type,
89
+ )
90
+ .map(
91
+ (p) =>
92
+ SwaggerComponentsExplorer.getParameter(props.swagger.components)(
93
+ p,
94
+ )!,
95
+ );
96
+ if (parameters.length === 0) return null;
97
+
98
+ const objects = parameters
99
+ .map((p) =>
100
+ SwaggerTypeChecker.isObject(p.schema)
101
+ ? p.schema
102
+ : SwaggerTypeChecker.isReference(p.schema) &&
103
+ SwaggerTypeChecker.isObject(
104
+ (props.swagger.components.schemas ?? {})[
105
+ p.schema.$ref.replace(`#/components/schemas/`, ``)
106
+ ] ?? {},
107
+ )
108
+ ? p.schema
109
+ : null!,
110
+ )
111
+ .filter((s) => !!s);
112
+ const primitives = parameters.filter(
113
+ (p) =>
114
+ SwaggerTypeChecker.isBoolean(p.schema) ||
115
+ SwaggerTypeChecker.isNumber(p.schema) ||
116
+ SwaggerTypeChecker.isInteger(p.schema) ||
117
+ SwaggerTypeChecker.isString(p.schema) ||
118
+ SwaggerTypeChecker.isArray(p.schema),
119
+ );
120
+ if (objects.length === 1 && primitives.length === 0) return objects[0];
121
+ else if (objects.length > 1) {
122
+ failures.push(
123
+ `${type} typed parameters must be only one object type`,
124
+ );
125
+ return false;
126
+ }
127
+
128
+ const dto: ISwaggerSchema.IObject | null = objects[0]
129
+ ? SwaggerTypeChecker.isObject(objects[0])
130
+ ? objects[0]
131
+ : ((props.swagger.components.schemas ?? {})[
132
+ (objects[0] as ISwaggerSchema.IReference).$ref.replace(
133
+ `#/components/schemas/`,
134
+ ``,
135
+ )
136
+ ] as ISwaggerSchema.IObject)
137
+ : null;
138
+ const entire: ISwaggerSchema.IObject[] = [
139
+ ...objects.map((o) =>
140
+ SwaggerTypeChecker.isObject(o)
141
+ ? o
142
+ : (props.swagger.components.schemas?.[
143
+ o.$ref.replace(`#/components/schemas/`, ``)
144
+ ]! as ISwaggerSchema.IObject),
145
+ ),
146
+ {
147
+ type: "object",
148
+ properties: Object.fromEntries([
149
+ ...primitives.map((p) => [
150
+ p.name,
151
+ {
152
+ ...p.schema,
153
+ description: p.schema.description ?? p.description,
154
+ },
155
+ ]),
156
+ ...(dto ? Object.entries(dto.properties ?? {}) : []),
157
+ ]),
158
+ required: [
159
+ ...primitives.filter((p) => p.required).map((p) => p.name!),
160
+ ...(dto ? dto.required ?? [] : []),
161
+ ],
162
+ },
163
+ ];
164
+ return parameters.length === 0
165
+ ? null
166
+ : emplaceReference(props.swagger)(
167
+ StringUtil.pascal(`I/Api/${endpoint.path}`) +
168
+ "." +
169
+ StringUtil.pascal(`${endpoint.method}/${type}`),
170
+ )({
171
+ type: "object",
172
+ properties: Object.fromEntries([
173
+ ...new Map<string, ISwaggerSchema>(
174
+ entire
175
+ .map((o) =>
176
+ Object.entries(o.properties ?? {}).map(
177
+ ([name, schema]) =>
178
+ [
179
+ name,
180
+ {
181
+ ...schema,
182
+ description:
183
+ schema.description ?? schema.description,
184
+ } as ISwaggerSchema,
185
+ ] as const,
186
+ ),
187
+ )
188
+ .flat(),
189
+ ),
190
+ ]),
191
+ required: [
192
+ ...new Set(entire.map((o) => o.required ?? []).flat()),
193
+ ],
194
+ });
195
+ });
196
+
197
+ const parameterNames: string[] = StringUtil.splitWithNormalization(
198
+ endpoint.path,
199
+ )
200
+ .filter((str) => str[0] === "{" || str[0] === ":")
201
+ .map((str) =>
202
+ str[0] === "{" ? str.substring(1, str.length - 1) : str.substring(1),
203
+ );
204
+ if (
205
+ parameterNames.length !==
206
+ (route.parameters ?? []).filter(
207
+ (p) =>
208
+ SwaggerComponentsExplorer.getParameter(props.swagger.components)(p)
209
+ ?.in === "path",
210
+ ).length
211
+ )
212
+ failures.push(
213
+ "number of path parameters are not matched with its full path.",
214
+ );
215
+
216
+ if (failures.length) {
217
+ console.log(
218
+ `Failed to migrate ${endpoint.method.toUpperCase()} ${endpoint.path}`,
219
+ ...failures.map((f) => ` - ${f}`),
220
+ );
221
+ return null;
222
+ }
223
+ return {
224
+ name: "@lazy",
225
+ originalPath: endpoint.path,
226
+ path: endpoint.path,
227
+ method: endpoint.method,
228
+ accessor: ["@lazy"],
229
+ headers: headers
230
+ ? {
231
+ name: "headers",
232
+ key: "headers",
233
+ schema: headers,
234
+ }
235
+ : null,
236
+ parameters: (route.parameters ?? [])
237
+ .map((p) =>
238
+ SwaggerComponentsExplorer.getParameter(props.swagger.components)(p),
239
+ )
240
+ .filter((p) => p !== null && p.in === "path")
241
+ .map((p, i) => ({
242
+ name: parameterNames[i],
243
+ key: (() => {
244
+ let key: string = StringUtil.normalize(parameterNames[i]);
245
+ if (Escaper.variable(key)) return key;
246
+ while (true) {
247
+ key = "_" + key;
248
+ if (!parameterNames.some((s) => s === key)) return key;
249
+ }
250
+ })(),
251
+ schema: {
252
+ ...p!.schema,
253
+ description: p!.schema.description ?? p!.description,
254
+ },
255
+ })),
256
+ query: query
257
+ ? {
258
+ name: "query",
259
+ key: "query",
260
+ schema: query,
261
+ }
262
+ : null,
263
+ body: body as IMigrateRoute.IBody | null,
264
+ success: success as IMigrateRoute.IBody | null,
265
+ exceptions: Object.fromEntries(
266
+ Object.entries(route.responses ?? {})
267
+ .filter(
268
+ ([key, value]) =>
269
+ key !== "200" &&
270
+ key !== "201" &&
271
+ key !== "default" &&
272
+ !!SwaggerComponentsExplorer.getResponse(
273
+ props.swagger.components,
274
+ )(value)?.content?.["application/json"],
275
+ )
276
+ .map(([key, value]) => {
277
+ const r = SwaggerComponentsExplorer.getResponse(
278
+ props.swagger.components,
279
+ )(value)!;
280
+ return [
281
+ key,
282
+ {
283
+ description: r.description,
284
+ schema: r.content?.["application/json"]?.schema ?? {},
285
+ },
286
+ ];
287
+ }),
288
+ ),
289
+ deprecated: route.deprecated ?? false,
290
+ comment: () => describe(props.swagger)(route),
291
+ tags: route.tags ?? [],
292
+ };
293
+ };
294
+
295
+ const describe =
296
+ (swagger: ISwagger) =>
297
+ (route: ISwaggerRoute): string => {
298
+ const commentTags: string[] = [];
299
+ const add = (text: string) => {
300
+ if (commentTags.every((line) => line !== text)) commentTags.push(text);
301
+ };
302
+
303
+ let description: string = route.description ?? "";
304
+ if (route.summary) {
305
+ const emended: string = route.summary.endsWith(".")
306
+ ? route.summary
307
+ : route.summary + ".";
308
+ if (
309
+ !!description.length &&
310
+ !description.startsWith(route.summary) &&
311
+ !route["x-nestia-jsDocTags"]?.some((t) => t.name === "summary")
312
+ )
313
+ description = `${emended}\n${description}`;
314
+ }
315
+ for (const p of route.parameters ?? []) {
316
+ const param: ISwaggerRouteParameter | null = (() => {
317
+ if (!typia.is<ISwaggerRouteParameter.IReference>(p))
318
+ return typia.is<ISwaggerRouteParameter>(p) ? p : null;
319
+ return (
320
+ swagger.components.parameters?.[
321
+ p.$ref.replace(`#/components/parameters/`, ``)
322
+ ] ?? null
323
+ );
324
+ })();
325
+ if (param !== null && param.description)
326
+ add(`@param ${param.name} ${param.description}`);
327
+ }
328
+ if (route.requestBody?.description)
329
+ add(`@param body ${route.requestBody.description}`);
330
+ for (const security of route.security ?? [])
331
+ for (const [name, scopes] of Object.entries(security))
332
+ add(`@security ${[name, ...scopes].join("")}`);
333
+ if (route.tags) route.tags.forEach((name) => add(`@tag ${name}`));
334
+ if (route.deprecated) add("@deprecated");
335
+ return description.length
336
+ ? commentTags.length
337
+ ? `${description}\n\n${commentTags.join("\n")}`
338
+ : description
339
+ : commentTags.join("\n");
340
+ };
341
+
342
+ const isNotObjectLiteral = (schema: ISwaggerSchema): boolean =>
343
+ SwaggerTypeChecker.isReference(schema) ||
344
+ SwaggerTypeChecker.isBoolean(schema) ||
345
+ SwaggerTypeChecker.isNumber(schema) ||
346
+ SwaggerTypeChecker.isString(schema) ||
347
+ SwaggerTypeChecker.isUnknown(schema) ||
348
+ (SwaggerTypeChecker.isAnyOf(schema) &&
349
+ schema.anyOf.every(isNotObjectLiteral)) ||
350
+ (SwaggerTypeChecker.isOneOf(schema) &&
351
+ schema.oneOf.every(isNotObjectLiteral)) ||
352
+ (SwaggerTypeChecker.isArray(schema) && isNotObjectLiteral(schema.items));
353
+
354
+ const emplaceBodySchema =
355
+ (from: "request" | "response") =>
356
+ (emplacer: (schema: ISwaggerSchema) => ISwaggerSchema.IReference) =>
357
+ (meta?: {
358
+ description?: string;
359
+ content?: ISwaggerRouteBodyContent;
360
+ "x-nestia-encrypted"?: boolean;
361
+ }): false | null | IMigrateRoute.IBody => {
362
+ if (!meta?.content) return null;
363
+
364
+ const entries: [string, { schema: ISwaggerSchema }][] = Object.entries(
365
+ meta.content,
366
+ );
367
+ const json = entries.find((e) =>
368
+ meta["x-nestia-encrypted"] === true
369
+ ? e[0].includes("text/plain") || e[0].includes("application/json")
370
+ : e[0].includes("application/json") || e[0].includes("*/*"),
371
+ );
372
+ if (json) {
373
+ const { schema } = json[1];
374
+ return {
375
+ type: "application/json",
376
+ name: "body",
377
+ key: "body",
378
+ schema: isNotObjectLiteral(schema) ? schema : emplacer(schema),
379
+ "x-nestia-encrypted": meta["x-nestia-encrypted"],
380
+ };
381
+ }
382
+
383
+ const query = entries.find((e) =>
384
+ e[0].includes("application/x-www-form-urlencoded"),
385
+ );
386
+ if (query) {
387
+ const { schema } = query[1];
388
+ return {
389
+ type: "application/x-www-form-urlencoded",
390
+ name: "body",
391
+ key: "body",
392
+ schema: isNotObjectLiteral(schema) ? schema : emplacer(schema),
393
+ };
394
+ }
395
+
396
+ const text = entries.find((e) => e[0].includes("text/plain"));
397
+ if (text)
398
+ return {
399
+ type: "text/plain",
400
+ name: "body",
401
+ key: "body",
402
+ schema: { type: "string" },
403
+ };
404
+
405
+ if (from === "request") {
406
+ const multipart = entries.find((e) =>
407
+ e[0].includes("multipart/form-data"),
408
+ );
409
+ if (multipart) {
410
+ const { schema } = multipart[1];
411
+ return {
412
+ type: "multipart/form-data",
413
+ name: "body",
414
+ key: "body",
415
+ schema: isNotObjectLiteral(schema) ? schema : emplacer(schema),
416
+ };
417
+ }
418
+ }
419
+ return false;
420
+ };
421
+
422
+ const emplaceReference =
423
+ (swagger: ISwagger) =>
424
+ (name: string) =>
425
+ (schema: ISwaggerSchema): ISwaggerSchema.IReference => {
426
+ swagger.components.schemas ??= {};
427
+ swagger.components.schemas[name] = schema;
428
+ return { $ref: `#/components/schemas/${name}` };
429
+ };
430
+ }
431
+
432
+ const SUPPORTED_METHODS: Set<string> = new Set([
433
+ "GET",
434
+ "POST",
435
+ "PUT",
436
+ "PATCH",
437
+ "DELETE",
438
+ "HEAD",
439
+ ]);