@nestia/migrate 8.0.5-dev.20250904-2 → 8.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,524 +1,524 @@
1
- import { OpenApi } from "@samchon/openapi";
2
- import ts from "typescript";
3
- import typia from "typia";
4
- import { TypeFactory } from "typia/lib/factories/TypeFactory";
5
- import { FormatCheatSheet } from "typia/lib/tags/internal/FormatCheatSheet";
6
- import { Escaper } from "typia/lib/utils/Escaper";
7
-
8
- import { FilePrinter } from "../utils/FilePrinter";
9
- import { OpenApiTypeChecker } from "../utils/OpenApiTypeChecker";
10
- import { StringUtil } from "../utils/StringUtil";
11
- import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
12
-
13
- export namespace NestiaMigrateSchemaProgrammer {
14
- /* -----------------------------------------------------------
15
- FACADE
16
- ----------------------------------------------------------- */
17
- export const write = (props: {
18
- components: OpenApi.IComponents;
19
- importer: NestiaMigrateImportProgrammer;
20
- schema: OpenApi.IJsonSchema;
21
- }): ts.TypeNode => {
22
- // CONSIDER ANY TYPE CASE
23
- const union: ts.TypeNode[] = [];
24
- if (OpenApiTypeChecker.isUnknown(props.schema))
25
- return TypeFactory.keyword("any");
26
-
27
- // ITERATION
28
- const type: ts.TypeNode = (() => {
29
- // ATOMIC
30
- if (OpenApiTypeChecker.isConstant(props.schema))
31
- return writeConstant({
32
- importer: props.importer,
33
- schema: props.schema,
34
- });
35
- else if (OpenApiTypeChecker.isBoolean(props.schema))
36
- return writeBoolean({
37
- importer: props.importer,
38
- schema: props.schema,
39
- });
40
- else if (OpenApiTypeChecker.isInteger(props.schema))
41
- return writeInteger({
42
- importer: props.importer,
43
- schema: props.schema,
44
- });
45
- else if (OpenApiTypeChecker.isNumber(props.schema))
46
- return writeNumber({
47
- importer: props.importer,
48
- schema: props.schema,
49
- });
50
- else if (OpenApiTypeChecker.isString(props.schema))
51
- return writeString({
52
- importer: props.importer,
53
- schema: props.schema,
54
- });
55
- // INSTANCES
56
- else if (OpenApiTypeChecker.isArray(props.schema))
57
- return writeArray({
58
- components: props.components,
59
- importer: props.importer,
60
- schema: props.schema,
61
- });
62
- else if (OpenApiTypeChecker.isTuple(props.schema))
63
- return writeTuple({
64
- components: props.components,
65
- importer: props.importer,
66
- schema: props.schema,
67
- });
68
- else if (OpenApiTypeChecker.isObject(props.schema))
69
- return writeObject({
70
- components: props.components,
71
- importer: props.importer,
72
- schema: props.schema,
73
- });
74
- else if (OpenApiTypeChecker.isReference(props.schema))
75
- return writeReference({
76
- importer: props.importer,
77
- schema: props.schema,
78
- });
79
- // UNION
80
- else if (OpenApiTypeChecker.isOneOf(props.schema))
81
- return writeUnion({
82
- components: props.components,
83
- importer: props.importer,
84
- elements: props.schema.oneOf,
85
- });
86
- else if (OpenApiTypeChecker.isNull(props.schema))
87
- return createNode("null");
88
- else return TypeFactory.keyword("any");
89
- })();
90
- union.push(type);
91
-
92
- // DETERMINE
93
- if (union.length === 0) return TypeFactory.keyword("any");
94
- else if (union.length === 1) return union[0];
95
- return ts.factory.createUnionTypeNode(union);
96
- };
97
-
98
- /* -----------------------------------------------------------
99
- ATOMICS
100
- ----------------------------------------------------------- */
101
- const writeConstant = (props: {
102
- importer: NestiaMigrateImportProgrammer;
103
- schema: OpenApi.IJsonSchema.IConstant;
104
- }): ts.TypeNode => {
105
- const intersection: ts.TypeNode[] = [
106
- ts.factory.createLiteralTypeNode(
107
- typeof props.schema.const === "boolean"
108
- ? props.schema.const === true
109
- ? ts.factory.createTrue()
110
- : ts.factory.createFalse()
111
- : typeof props.schema.const === "number"
112
- ? props.schema.const < 0
113
- ? ts.factory.createPrefixUnaryExpression(
114
- ts.SyntaxKind.MinusToken,
115
- ts.factory.createNumericLiteral(-props.schema.const),
116
- )
117
- : ts.factory.createNumericLiteral(props.schema.const)
118
- : ts.factory.createStringLiteral(props.schema.const),
119
- ),
120
- ];
121
- writePlugin({
122
- importer: props.importer,
123
- schema: props.schema,
124
- regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IConstant>(),
125
- intersection,
126
- });
127
- return intersection.length === 1
128
- ? intersection[0]
129
- : ts.factory.createIntersectionTypeNode(intersection);
130
- };
131
-
132
- const writeBoolean = (props: {
133
- importer: NestiaMigrateImportProgrammer;
134
- schema: OpenApi.IJsonSchema.IBoolean;
135
- }): ts.TypeNode => {
136
- const intersection: ts.TypeNode[] = [TypeFactory.keyword("boolean")];
137
- writePlugin({
138
- importer: props.importer,
139
- regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IBoolean>(),
140
- intersection,
141
- schema: props.schema,
142
- });
143
- return intersection.length === 1
144
- ? intersection[0]
145
- : ts.factory.createIntersectionTypeNode(intersection);
146
- };
147
-
148
- const writeInteger = (props: {
149
- importer: NestiaMigrateImportProgrammer;
150
- schema: OpenApi.IJsonSchema.IInteger;
151
- }): ts.TypeNode =>
152
- writeNumeric({
153
- factory: () => [
154
- TypeFactory.keyword("number"),
155
- props.importer.tag("Type", "int32"),
156
- ],
157
- importer: props.importer,
158
- schema: props.schema,
159
- });
160
-
161
- const writeNumber = (props: {
162
- importer: NestiaMigrateImportProgrammer;
163
- schema: OpenApi.IJsonSchema.INumber;
164
- }): ts.TypeNode =>
165
- writeNumeric({
166
- factory: () => [TypeFactory.keyword("number")],
167
- importer: props.importer,
168
- schema: props.schema,
169
- });
170
-
171
- const writeNumeric = (props: {
172
- factory: () => ts.TypeNode[];
173
- importer: NestiaMigrateImportProgrammer;
174
- schema: OpenApi.IJsonSchema.IInteger | OpenApi.IJsonSchema.INumber;
175
- }): ts.TypeNode => {
176
- const intersection: ts.TypeNode[] = props.factory();
177
- if (props.schema.default !== undefined)
178
- intersection.push(props.importer.tag("Default", props.schema.default));
179
- if (props.schema.minimum !== undefined)
180
- intersection.push(
181
- props.importer.tag(
182
- props.schema.exclusiveMinimum ? "ExclusiveMinimum" : "Minimum",
183
- props.schema.minimum,
184
- ),
185
- );
186
- if (props.schema.maximum !== undefined)
187
- intersection.push(
188
- props.importer.tag(
189
- props.schema.exclusiveMaximum ? "ExclusiveMaximum" : "Maximum",
190
- props.schema.maximum,
191
- ),
192
- );
193
- if (props.schema.multipleOf !== undefined)
194
- intersection.push(
195
- props.importer.tag("MultipleOf", props.schema.multipleOf),
196
- );
197
- writePlugin({
198
- importer: props.importer,
199
- regular: typia.misc.literals<keyof OpenApi.IJsonSchema.INumber>(),
200
- intersection,
201
- schema: props.schema,
202
- });
203
- return intersection.length === 1
204
- ? intersection[0]
205
- : ts.factory.createIntersectionTypeNode(intersection);
206
- };
207
-
208
- const writeString = (props: {
209
- importer: NestiaMigrateImportProgrammer;
210
- schema: OpenApi.IJsonSchema.IString;
211
- }): ts.TypeNode => {
212
- if (props.schema.format === "binary")
213
- return ts.factory.createTypeReferenceNode("File");
214
-
215
- const intersection: ts.TypeNode[] = [TypeFactory.keyword("string")];
216
- if (props.schema.default !== undefined)
217
- intersection.push(props.importer.tag("Default", props.schema.default));
218
- if (props.schema.minLength !== undefined)
219
- intersection.push(
220
- props.importer.tag("MinLength", props.schema.minLength),
221
- );
222
- if (props.schema.maxLength !== undefined)
223
- intersection.push(
224
- props.importer.tag("MaxLength", props.schema.maxLength),
225
- );
226
- if (props.schema.pattern !== undefined)
227
- intersection.push(props.importer.tag("Pattern", props.schema.pattern));
228
- if (
229
- props.schema.format !== undefined &&
230
- (FormatCheatSheet as Record<string, string>)[props.schema.format] !==
231
- undefined
232
- )
233
- intersection.push(props.importer.tag("Format", props.schema.format));
234
- if (props.schema.contentMediaType !== undefined)
235
- intersection.push(
236
- props.importer.tag("ContentMediaType", props.schema.contentMediaType),
237
- );
238
- writePlugin({
239
- importer: props.importer,
240
- regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IString>(),
241
- intersection,
242
- schema: props.schema,
243
- });
244
- return intersection.length === 1
245
- ? intersection[0]
246
- : ts.factory.createIntersectionTypeNode(intersection);
247
- };
248
-
249
- /* -----------------------------------------------------------
250
- INSTANCES
251
- ----------------------------------------------------------- */
252
- const writeArray = (props: {
253
- components: OpenApi.IComponents;
254
- importer: NestiaMigrateImportProgrammer;
255
- schema: OpenApi.IJsonSchema.IArray;
256
- }): ts.TypeNode => {
257
- const intersection: ts.TypeNode[] = [
258
- ts.factory.createArrayTypeNode(
259
- write({
260
- components: props.components,
261
- importer: props.importer,
262
- schema: props.schema.items,
263
- }),
264
- ),
265
- ];
266
- if (props.schema.minItems !== undefined)
267
- intersection.push(props.importer.tag("MinItems", props.schema.minItems));
268
- if (props.schema.maxItems !== undefined)
269
- intersection.push(props.importer.tag("MaxItems", props.schema.maxItems));
270
- if (props.schema.uniqueItems === true)
271
- intersection.push(props.importer.tag("UniqueItems"));
272
- writePlugin({
273
- importer: props.importer,
274
- regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IArray>(),
275
- intersection,
276
- schema: props.schema,
277
- });
278
- return intersection.length === 1
279
- ? intersection[0]
280
- : ts.factory.createIntersectionTypeNode(intersection);
281
- };
282
-
283
- const writeTuple = (props: {
284
- components: OpenApi.IComponents;
285
- importer: NestiaMigrateImportProgrammer;
286
- schema: OpenApi.IJsonSchema.ITuple;
287
- }): ts.TypeNode => {
288
- const tuple: ts.TypeNode = ts.factory.createTupleTypeNode([
289
- ...props.schema.prefixItems.map((item) =>
290
- write({
291
- components: props.components,
292
- importer: props.importer,
293
- schema: item,
294
- }),
295
- ),
296
- ...(typeof props.schema.additionalItems === "object" &&
297
- props.schema.additionalItems !== null
298
- ? [
299
- ts.factory.createRestTypeNode(
300
- write({
301
- components: props.components,
302
- importer: props.importer,
303
- schema: props.schema.additionalItems,
304
- }),
305
- ),
306
- ]
307
- : props.schema.additionalItems === true
308
- ? [
309
- ts.factory.createRestTypeNode(
310
- ts.factory.createArrayTypeNode(
311
- ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
312
- ),
313
- ),
314
- ]
315
- : []),
316
- ]);
317
- const intersection: ts.TypeNode[] = [tuple];
318
- writePlugin({
319
- importer: props.importer,
320
- regular: typia.misc.literals<keyof OpenApi.IJsonSchema.ITuple>(),
321
- intersection,
322
- schema: props.schema,
323
- });
324
- return intersection.length === 1
325
- ? intersection[0]
326
- : ts.factory.createIntersectionTypeNode(intersection);
327
- };
328
-
329
- const writeObject = (props: {
330
- components: OpenApi.IComponents;
331
- importer: NestiaMigrateImportProgrammer;
332
- schema: OpenApi.IJsonSchema.IObject;
333
- }): ts.TypeNode => {
334
- const regular = () =>
335
- ts.factory.createTypeLiteralNode(
336
- Object.entries(props.schema.properties ?? [])
337
- .map(([key, value], index) => [
338
- ...(index !== 0 &&
339
- (!!value.title?.length || !!value.description?.length)
340
- ? [ts.factory.createIdentifier("\n") as any]
341
- : []),
342
- writeRegularProperty({
343
- components: props.components,
344
- importer: props.importer,
345
- required: props.schema.required ?? [],
346
- key,
347
- value,
348
- }),
349
- ])
350
- .flat(),
351
- );
352
- const dynamic = () =>
353
- ts.factory.createTypeLiteralNode([
354
- writeDynamicProperty({
355
- components: props.components,
356
- importer: props.importer,
357
- schema: props.schema.additionalProperties as OpenApi.IJsonSchema,
358
- }),
359
- ]);
360
- return !!props.schema.properties?.length &&
361
- typeof props.schema.additionalProperties === "object"
362
- ? ts.factory.createIntersectionTypeNode([regular(), dynamic()])
363
- : typeof props.schema.additionalProperties === "object"
364
- ? dynamic()
365
- : regular();
366
- };
367
-
368
- const writeRegularProperty = (props: {
369
- components: OpenApi.IComponents;
370
- importer: NestiaMigrateImportProgrammer;
371
- required: string[];
372
- key: string;
373
- value: OpenApi.IJsonSchema;
374
- }) => {
375
- const valueTypeNode: ts.TypeNode = write({
376
- components: props.components,
377
- importer: props.importer,
378
- schema: props.value,
379
- });
380
- return FilePrinter.description(
381
- ts.factory.createPropertySignature(
382
- undefined,
383
- Escaper.variable(props.key)
384
- ? ts.factory.createIdentifier(props.key)
385
- : ts.factory.createStringLiteral(props.key),
386
- props.required.includes(props.key)
387
- ? undefined
388
- : ts.factory.createToken(ts.SyntaxKind.QuestionToken),
389
- props.required.includes(props.key)
390
- ? valueTypeNode
391
- : ts.isUnionTypeNode(valueTypeNode)
392
- ? ts.factory.createUnionTypeNode([
393
- ...valueTypeNode.types,
394
- ts.factory.createTypeReferenceNode("undefined"),
395
- ])
396
- : ts.factory.createUnionTypeNode([
397
- valueTypeNode,
398
- ts.factory.createTypeReferenceNode("undefined"),
399
- ]),
400
- ),
401
- writeComment(props.value),
402
- );
403
- };
404
-
405
- const writeDynamicProperty = (props: {
406
- components: OpenApi.IComponents;
407
- importer: NestiaMigrateImportProgrammer;
408
- schema: OpenApi.IJsonSchema;
409
- }) =>
410
- FilePrinter.description(
411
- ts.factory.createIndexSignature(
412
- undefined,
413
- [
414
- ts.factory.createParameterDeclaration(
415
- undefined,
416
- undefined,
417
- ts.factory.createIdentifier("key"),
418
- undefined,
419
- TypeFactory.keyword("string"),
420
- ),
421
- ],
422
- write(props),
423
- ),
424
- writeComment(props.schema),
425
- );
426
-
427
- const writeReference = (props: {
428
- importer: NestiaMigrateImportProgrammer;
429
- schema: OpenApi.IJsonSchema.IReference;
430
- }): ts.TypeReferenceNode | ts.KeywordTypeNode => {
431
- if (props.schema.$ref.startsWith("#/components/schemas") === false)
432
- return TypeFactory.keyword("any");
433
- const name: string = props.schema.$ref
434
- .split("/")
435
- .slice(3)
436
- .filter((str) => str.length !== 0)
437
- .map(StringUtil.escapeNonVariable)
438
- .join("");
439
- if (name === "") return TypeFactory.keyword("any");
440
- return props.importer.dto(name);
441
- };
442
-
443
- /* -----------------------------------------------------------
444
- UNIONS
445
- ----------------------------------------------------------- */
446
- const writeUnion = (props: {
447
- components: OpenApi.IComponents;
448
- importer: NestiaMigrateImportProgrammer;
449
- elements: OpenApi.IJsonSchema[];
450
- }): ts.UnionTypeNode =>
451
- ts.factory.createUnionTypeNode(
452
- props.elements.map((schema) =>
453
- write({
454
- components: props.components,
455
- importer: props.importer,
456
- schema,
457
- }),
458
- ),
459
- );
460
- }
461
- const createNode = (text: string) => ts.factory.createTypeReferenceNode(text);
462
-
463
- const writeComment = (schema: OpenApi.IJsonSchema): string =>
464
- [
465
- ...(schema.description?.length
466
- ? [eraseCommentTags(schema.description)]
467
- : []),
468
- ...(schema.description?.length &&
469
- (schema.title !== undefined || schema.deprecated === true)
470
- ? [""]
471
- : []),
472
- ...(schema.title !== undefined ? [`@title ${schema.title}`] : []),
473
- ...(schema.deprecated === true ? [`@deprecated`] : []),
474
- ]
475
- .join("\n")
476
- .split("*/")
477
- .join("*\\/");
478
-
479
- const eraseCommentTags = (description: string): string => {
480
- const lines: string[] = description.split("\n");
481
- return lines
482
- .filter((s) => COMMENT_TAGS.every((tag) => !s.includes(tag)))
483
- .join("\n");
484
- };
485
-
486
- const writePlugin = (props: {
487
- importer: NestiaMigrateImportProgrammer;
488
- regular: string[];
489
- intersection: ts.TypeNode[];
490
- schema: Record<string, any>;
491
- }) => {
492
- const extra: any = {};
493
- for (const [key, value] of Object.entries(props.schema))
494
- if (
495
- value !== undefined &&
496
- false === props.regular.includes(key) &&
497
- key.startsWith("x-")
498
- )
499
- extra[key] = value;
500
- if (Object.keys(extra).length !== 0)
501
- props.intersection.push(props.importer.tag("JsonSchemaPlugin", extra));
502
- };
503
-
504
- const COMMENT_TAGS = [
505
- // string
506
- "@format",
507
- "@pattern",
508
- "@length",
509
- "@minLength",
510
- "@maxLength",
511
- "@contentMediaType",
512
- // number
513
- "@type",
514
- "@minimum",
515
- "@maximum",
516
- "@exclusiveMinimum",
517
- "@exclusiveMaximum",
518
- "@multipleOf",
519
- // array
520
- "@items",
521
- "@minItems",
522
- "@maxItems",
523
- "@uniqueItems",
524
- ];
1
+ import { OpenApi } from "@samchon/openapi";
2
+ import ts from "typescript";
3
+ import typia from "typia";
4
+ import { TypeFactory } from "typia/lib/factories/TypeFactory";
5
+ import { FormatCheatSheet } from "typia/lib/tags/internal/FormatCheatSheet";
6
+ import { Escaper } from "typia/lib/utils/Escaper";
7
+
8
+ import { FilePrinter } from "../utils/FilePrinter";
9
+ import { OpenApiTypeChecker } from "../utils/OpenApiTypeChecker";
10
+ import { StringUtil } from "../utils/StringUtil";
11
+ import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
12
+
13
+ export namespace NestiaMigrateSchemaProgrammer {
14
+ /* -----------------------------------------------------------
15
+ FACADE
16
+ ----------------------------------------------------------- */
17
+ export const write = (props: {
18
+ components: OpenApi.IComponents;
19
+ importer: NestiaMigrateImportProgrammer;
20
+ schema: OpenApi.IJsonSchema;
21
+ }): ts.TypeNode => {
22
+ // CONSIDER ANY TYPE CASE
23
+ const union: ts.TypeNode[] = [];
24
+ if (OpenApiTypeChecker.isUnknown(props.schema))
25
+ return TypeFactory.keyword("any");
26
+
27
+ // ITERATION
28
+ const type: ts.TypeNode = (() => {
29
+ // ATOMIC
30
+ if (OpenApiTypeChecker.isConstant(props.schema))
31
+ return writeConstant({
32
+ importer: props.importer,
33
+ schema: props.schema,
34
+ });
35
+ else if (OpenApiTypeChecker.isBoolean(props.schema))
36
+ return writeBoolean({
37
+ importer: props.importer,
38
+ schema: props.schema,
39
+ });
40
+ else if (OpenApiTypeChecker.isInteger(props.schema))
41
+ return writeInteger({
42
+ importer: props.importer,
43
+ schema: props.schema,
44
+ });
45
+ else if (OpenApiTypeChecker.isNumber(props.schema))
46
+ return writeNumber({
47
+ importer: props.importer,
48
+ schema: props.schema,
49
+ });
50
+ else if (OpenApiTypeChecker.isString(props.schema))
51
+ return writeString({
52
+ importer: props.importer,
53
+ schema: props.schema,
54
+ });
55
+ // INSTANCES
56
+ else if (OpenApiTypeChecker.isArray(props.schema))
57
+ return writeArray({
58
+ components: props.components,
59
+ importer: props.importer,
60
+ schema: props.schema,
61
+ });
62
+ else if (OpenApiTypeChecker.isTuple(props.schema))
63
+ return writeTuple({
64
+ components: props.components,
65
+ importer: props.importer,
66
+ schema: props.schema,
67
+ });
68
+ else if (OpenApiTypeChecker.isObject(props.schema))
69
+ return writeObject({
70
+ components: props.components,
71
+ importer: props.importer,
72
+ schema: props.schema,
73
+ });
74
+ else if (OpenApiTypeChecker.isReference(props.schema))
75
+ return writeReference({
76
+ importer: props.importer,
77
+ schema: props.schema,
78
+ });
79
+ // UNION
80
+ else if (OpenApiTypeChecker.isOneOf(props.schema))
81
+ return writeUnion({
82
+ components: props.components,
83
+ importer: props.importer,
84
+ elements: props.schema.oneOf,
85
+ });
86
+ else if (OpenApiTypeChecker.isNull(props.schema))
87
+ return createNode("null");
88
+ else return TypeFactory.keyword("any");
89
+ })();
90
+ union.push(type);
91
+
92
+ // DETERMINE
93
+ if (union.length === 0) return TypeFactory.keyword("any");
94
+ else if (union.length === 1) return union[0];
95
+ return ts.factory.createUnionTypeNode(union);
96
+ };
97
+
98
+ /* -----------------------------------------------------------
99
+ ATOMICS
100
+ ----------------------------------------------------------- */
101
+ const writeConstant = (props: {
102
+ importer: NestiaMigrateImportProgrammer;
103
+ schema: OpenApi.IJsonSchema.IConstant;
104
+ }): ts.TypeNode => {
105
+ const intersection: ts.TypeNode[] = [
106
+ ts.factory.createLiteralTypeNode(
107
+ typeof props.schema.const === "boolean"
108
+ ? props.schema.const === true
109
+ ? ts.factory.createTrue()
110
+ : ts.factory.createFalse()
111
+ : typeof props.schema.const === "number"
112
+ ? props.schema.const < 0
113
+ ? ts.factory.createPrefixUnaryExpression(
114
+ ts.SyntaxKind.MinusToken,
115
+ ts.factory.createNumericLiteral(-props.schema.const),
116
+ )
117
+ : ts.factory.createNumericLiteral(props.schema.const)
118
+ : ts.factory.createStringLiteral(props.schema.const),
119
+ ),
120
+ ];
121
+ writePlugin({
122
+ importer: props.importer,
123
+ schema: props.schema,
124
+ regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IConstant>(),
125
+ intersection,
126
+ });
127
+ return intersection.length === 1
128
+ ? intersection[0]
129
+ : ts.factory.createIntersectionTypeNode(intersection);
130
+ };
131
+
132
+ const writeBoolean = (props: {
133
+ importer: NestiaMigrateImportProgrammer;
134
+ schema: OpenApi.IJsonSchema.IBoolean;
135
+ }): ts.TypeNode => {
136
+ const intersection: ts.TypeNode[] = [TypeFactory.keyword("boolean")];
137
+ writePlugin({
138
+ importer: props.importer,
139
+ regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IBoolean>(),
140
+ intersection,
141
+ schema: props.schema,
142
+ });
143
+ return intersection.length === 1
144
+ ? intersection[0]
145
+ : ts.factory.createIntersectionTypeNode(intersection);
146
+ };
147
+
148
+ const writeInteger = (props: {
149
+ importer: NestiaMigrateImportProgrammer;
150
+ schema: OpenApi.IJsonSchema.IInteger;
151
+ }): ts.TypeNode =>
152
+ writeNumeric({
153
+ factory: () => [
154
+ TypeFactory.keyword("number"),
155
+ props.importer.tag("Type", "int32"),
156
+ ],
157
+ importer: props.importer,
158
+ schema: props.schema,
159
+ });
160
+
161
+ const writeNumber = (props: {
162
+ importer: NestiaMigrateImportProgrammer;
163
+ schema: OpenApi.IJsonSchema.INumber;
164
+ }): ts.TypeNode =>
165
+ writeNumeric({
166
+ factory: () => [TypeFactory.keyword("number")],
167
+ importer: props.importer,
168
+ schema: props.schema,
169
+ });
170
+
171
+ const writeNumeric = (props: {
172
+ factory: () => ts.TypeNode[];
173
+ importer: NestiaMigrateImportProgrammer;
174
+ schema: OpenApi.IJsonSchema.IInteger | OpenApi.IJsonSchema.INumber;
175
+ }): ts.TypeNode => {
176
+ const intersection: ts.TypeNode[] = props.factory();
177
+ if (props.schema.default !== undefined)
178
+ intersection.push(props.importer.tag("Default", props.schema.default));
179
+ if (props.schema.minimum !== undefined)
180
+ intersection.push(
181
+ props.importer.tag(
182
+ props.schema.exclusiveMinimum ? "ExclusiveMinimum" : "Minimum",
183
+ props.schema.minimum,
184
+ ),
185
+ );
186
+ if (props.schema.maximum !== undefined)
187
+ intersection.push(
188
+ props.importer.tag(
189
+ props.schema.exclusiveMaximum ? "ExclusiveMaximum" : "Maximum",
190
+ props.schema.maximum,
191
+ ),
192
+ );
193
+ if (props.schema.multipleOf !== undefined)
194
+ intersection.push(
195
+ props.importer.tag("MultipleOf", props.schema.multipleOf),
196
+ );
197
+ writePlugin({
198
+ importer: props.importer,
199
+ regular: typia.misc.literals<keyof OpenApi.IJsonSchema.INumber>(),
200
+ intersection,
201
+ schema: props.schema,
202
+ });
203
+ return intersection.length === 1
204
+ ? intersection[0]
205
+ : ts.factory.createIntersectionTypeNode(intersection);
206
+ };
207
+
208
+ const writeString = (props: {
209
+ importer: NestiaMigrateImportProgrammer;
210
+ schema: OpenApi.IJsonSchema.IString;
211
+ }): ts.TypeNode => {
212
+ if (props.schema.format === "binary")
213
+ return ts.factory.createTypeReferenceNode("File");
214
+
215
+ const intersection: ts.TypeNode[] = [TypeFactory.keyword("string")];
216
+ if (props.schema.default !== undefined)
217
+ intersection.push(props.importer.tag("Default", props.schema.default));
218
+ if (props.schema.minLength !== undefined)
219
+ intersection.push(
220
+ props.importer.tag("MinLength", props.schema.minLength),
221
+ );
222
+ if (props.schema.maxLength !== undefined)
223
+ intersection.push(
224
+ props.importer.tag("MaxLength", props.schema.maxLength),
225
+ );
226
+ if (props.schema.pattern !== undefined)
227
+ intersection.push(props.importer.tag("Pattern", props.schema.pattern));
228
+ if (
229
+ props.schema.format !== undefined &&
230
+ (FormatCheatSheet as Record<string, string>)[props.schema.format] !==
231
+ undefined
232
+ )
233
+ intersection.push(props.importer.tag("Format", props.schema.format));
234
+ if (props.schema.contentMediaType !== undefined)
235
+ intersection.push(
236
+ props.importer.tag("ContentMediaType", props.schema.contentMediaType),
237
+ );
238
+ writePlugin({
239
+ importer: props.importer,
240
+ regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IString>(),
241
+ intersection,
242
+ schema: props.schema,
243
+ });
244
+ return intersection.length === 1
245
+ ? intersection[0]
246
+ : ts.factory.createIntersectionTypeNode(intersection);
247
+ };
248
+
249
+ /* -----------------------------------------------------------
250
+ INSTANCES
251
+ ----------------------------------------------------------- */
252
+ const writeArray = (props: {
253
+ components: OpenApi.IComponents;
254
+ importer: NestiaMigrateImportProgrammer;
255
+ schema: OpenApi.IJsonSchema.IArray;
256
+ }): ts.TypeNode => {
257
+ const intersection: ts.TypeNode[] = [
258
+ ts.factory.createArrayTypeNode(
259
+ write({
260
+ components: props.components,
261
+ importer: props.importer,
262
+ schema: props.schema.items,
263
+ }),
264
+ ),
265
+ ];
266
+ if (props.schema.minItems !== undefined)
267
+ intersection.push(props.importer.tag("MinItems", props.schema.minItems));
268
+ if (props.schema.maxItems !== undefined)
269
+ intersection.push(props.importer.tag("MaxItems", props.schema.maxItems));
270
+ if (props.schema.uniqueItems === true)
271
+ intersection.push(props.importer.tag("UniqueItems"));
272
+ writePlugin({
273
+ importer: props.importer,
274
+ regular: typia.misc.literals<keyof OpenApi.IJsonSchema.IArray>(),
275
+ intersection,
276
+ schema: props.schema,
277
+ });
278
+ return intersection.length === 1
279
+ ? intersection[0]
280
+ : ts.factory.createIntersectionTypeNode(intersection);
281
+ };
282
+
283
+ const writeTuple = (props: {
284
+ components: OpenApi.IComponents;
285
+ importer: NestiaMigrateImportProgrammer;
286
+ schema: OpenApi.IJsonSchema.ITuple;
287
+ }): ts.TypeNode => {
288
+ const tuple: ts.TypeNode = ts.factory.createTupleTypeNode([
289
+ ...props.schema.prefixItems.map((item) =>
290
+ write({
291
+ components: props.components,
292
+ importer: props.importer,
293
+ schema: item,
294
+ }),
295
+ ),
296
+ ...(typeof props.schema.additionalItems === "object" &&
297
+ props.schema.additionalItems !== null
298
+ ? [
299
+ ts.factory.createRestTypeNode(
300
+ write({
301
+ components: props.components,
302
+ importer: props.importer,
303
+ schema: props.schema.additionalItems,
304
+ }),
305
+ ),
306
+ ]
307
+ : props.schema.additionalItems === true
308
+ ? [
309
+ ts.factory.createRestTypeNode(
310
+ ts.factory.createArrayTypeNode(
311
+ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
312
+ ),
313
+ ),
314
+ ]
315
+ : []),
316
+ ]);
317
+ const intersection: ts.TypeNode[] = [tuple];
318
+ writePlugin({
319
+ importer: props.importer,
320
+ regular: typia.misc.literals<keyof OpenApi.IJsonSchema.ITuple>(),
321
+ intersection,
322
+ schema: props.schema,
323
+ });
324
+ return intersection.length === 1
325
+ ? intersection[0]
326
+ : ts.factory.createIntersectionTypeNode(intersection);
327
+ };
328
+
329
+ const writeObject = (props: {
330
+ components: OpenApi.IComponents;
331
+ importer: NestiaMigrateImportProgrammer;
332
+ schema: OpenApi.IJsonSchema.IObject;
333
+ }): ts.TypeNode => {
334
+ const regular = () =>
335
+ ts.factory.createTypeLiteralNode(
336
+ Object.entries(props.schema.properties ?? [])
337
+ .map(([key, value], index) => [
338
+ ...(index !== 0 &&
339
+ (!!value.title?.length || !!value.description?.length)
340
+ ? [ts.factory.createIdentifier("\n") as any]
341
+ : []),
342
+ writeRegularProperty({
343
+ components: props.components,
344
+ importer: props.importer,
345
+ required: props.schema.required ?? [],
346
+ key,
347
+ value,
348
+ }),
349
+ ])
350
+ .flat(),
351
+ );
352
+ const dynamic = () =>
353
+ ts.factory.createTypeLiteralNode([
354
+ writeDynamicProperty({
355
+ components: props.components,
356
+ importer: props.importer,
357
+ schema: props.schema.additionalProperties as OpenApi.IJsonSchema,
358
+ }),
359
+ ]);
360
+ return !!props.schema.properties?.length &&
361
+ typeof props.schema.additionalProperties === "object"
362
+ ? ts.factory.createIntersectionTypeNode([regular(), dynamic()])
363
+ : typeof props.schema.additionalProperties === "object"
364
+ ? dynamic()
365
+ : regular();
366
+ };
367
+
368
+ const writeRegularProperty = (props: {
369
+ components: OpenApi.IComponents;
370
+ importer: NestiaMigrateImportProgrammer;
371
+ required: string[];
372
+ key: string;
373
+ value: OpenApi.IJsonSchema;
374
+ }) => {
375
+ const valueTypeNode: ts.TypeNode = write({
376
+ components: props.components,
377
+ importer: props.importer,
378
+ schema: props.value,
379
+ });
380
+ return FilePrinter.description(
381
+ ts.factory.createPropertySignature(
382
+ undefined,
383
+ Escaper.variable(props.key)
384
+ ? ts.factory.createIdentifier(props.key)
385
+ : ts.factory.createStringLiteral(props.key),
386
+ props.required.includes(props.key)
387
+ ? undefined
388
+ : ts.factory.createToken(ts.SyntaxKind.QuestionToken),
389
+ props.required.includes(props.key)
390
+ ? valueTypeNode
391
+ : ts.isUnionTypeNode(valueTypeNode)
392
+ ? ts.factory.createUnionTypeNode([
393
+ ...valueTypeNode.types,
394
+ ts.factory.createTypeReferenceNode("undefined"),
395
+ ])
396
+ : ts.factory.createUnionTypeNode([
397
+ valueTypeNode,
398
+ ts.factory.createTypeReferenceNode("undefined"),
399
+ ]),
400
+ ),
401
+ writeComment(props.value),
402
+ );
403
+ };
404
+
405
+ const writeDynamicProperty = (props: {
406
+ components: OpenApi.IComponents;
407
+ importer: NestiaMigrateImportProgrammer;
408
+ schema: OpenApi.IJsonSchema;
409
+ }) =>
410
+ FilePrinter.description(
411
+ ts.factory.createIndexSignature(
412
+ undefined,
413
+ [
414
+ ts.factory.createParameterDeclaration(
415
+ undefined,
416
+ undefined,
417
+ ts.factory.createIdentifier("key"),
418
+ undefined,
419
+ TypeFactory.keyword("string"),
420
+ ),
421
+ ],
422
+ write(props),
423
+ ),
424
+ writeComment(props.schema),
425
+ );
426
+
427
+ const writeReference = (props: {
428
+ importer: NestiaMigrateImportProgrammer;
429
+ schema: OpenApi.IJsonSchema.IReference;
430
+ }): ts.TypeReferenceNode | ts.KeywordTypeNode => {
431
+ if (props.schema.$ref.startsWith("#/components/schemas") === false)
432
+ return TypeFactory.keyword("any");
433
+ const name: string = props.schema.$ref
434
+ .split("/")
435
+ .slice(3)
436
+ .filter((str) => str.length !== 0)
437
+ .map(StringUtil.escapeNonVariable)
438
+ .join("");
439
+ if (name === "") return TypeFactory.keyword("any");
440
+ return props.importer.dto(name);
441
+ };
442
+
443
+ /* -----------------------------------------------------------
444
+ UNIONS
445
+ ----------------------------------------------------------- */
446
+ const writeUnion = (props: {
447
+ components: OpenApi.IComponents;
448
+ importer: NestiaMigrateImportProgrammer;
449
+ elements: OpenApi.IJsonSchema[];
450
+ }): ts.UnionTypeNode =>
451
+ ts.factory.createUnionTypeNode(
452
+ props.elements.map((schema) =>
453
+ write({
454
+ components: props.components,
455
+ importer: props.importer,
456
+ schema,
457
+ }),
458
+ ),
459
+ );
460
+ }
461
+ const createNode = (text: string) => ts.factory.createTypeReferenceNode(text);
462
+
463
+ const writeComment = (schema: OpenApi.IJsonSchema): string =>
464
+ [
465
+ ...(schema.description?.length
466
+ ? [eraseCommentTags(schema.description)]
467
+ : []),
468
+ ...(schema.description?.length &&
469
+ (schema.title !== undefined || schema.deprecated === true)
470
+ ? [""]
471
+ : []),
472
+ ...(schema.title !== undefined ? [`@title ${schema.title}`] : []),
473
+ ...(schema.deprecated === true ? [`@deprecated`] : []),
474
+ ]
475
+ .join("\n")
476
+ .split("*/")
477
+ .join("*\\/");
478
+
479
+ const eraseCommentTags = (description: string): string => {
480
+ const lines: string[] = description.split("\n");
481
+ return lines
482
+ .filter((s) => COMMENT_TAGS.every((tag) => !s.includes(tag)))
483
+ .join("\n");
484
+ };
485
+
486
+ const writePlugin = (props: {
487
+ importer: NestiaMigrateImportProgrammer;
488
+ regular: string[];
489
+ intersection: ts.TypeNode[];
490
+ schema: Record<string, any>;
491
+ }) => {
492
+ const extra: any = {};
493
+ for (const [key, value] of Object.entries(props.schema))
494
+ if (
495
+ value !== undefined &&
496
+ false === props.regular.includes(key) &&
497
+ key.startsWith("x-")
498
+ )
499
+ extra[key] = value;
500
+ if (Object.keys(extra).length !== 0)
501
+ props.intersection.push(props.importer.tag("JsonSchemaPlugin", extra));
502
+ };
503
+
504
+ const COMMENT_TAGS = [
505
+ // string
506
+ "@format",
507
+ "@pattern",
508
+ "@length",
509
+ "@minLength",
510
+ "@maxLength",
511
+ "@contentMediaType",
512
+ // number
513
+ "@type",
514
+ "@minimum",
515
+ "@maximum",
516
+ "@exclusiveMinimum",
517
+ "@exclusiveMaximum",
518
+ "@multipleOf",
519
+ // array
520
+ "@items",
521
+ "@minItems",
522
+ "@maxItems",
523
+ "@uniqueItems",
524
+ ];