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