From 1e9dea46132b5162e1c047aee8e977179aaebe5a Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Tue, 2 Apr 2024 20:59:51 +0900 Subject: [PATCH] Adapt `typia`s `JsonSchemaPlugin` tag in `@nestia/editor` . --- packages/migrate/assets/input/bbs.json | 4 +- packages/migrate/package.json | 6 +- .../src/factories/TypeLiteralFactory.ts | 50 +++++++++++++++ .../programmers/MigrateImportProgrammer.ts | 10 +-- .../programmers/MigrateSchemaProgrammer.ts | 62 ++++++++++++++++--- .../migrate/src/structures/ISwaggerSchema.ts | 1 + website/package.json | 4 +- 7 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 packages/migrate/src/factories/TypeLiteralFactory.ts diff --git a/packages/migrate/assets/input/bbs.json b/packages/migrate/assets/input/bbs.json index 94fb441c5..44c86044d 100644 --- a/packages/migrate/assets/input/bbs.json +++ b/packages/migrate/assets/input/bbs.json @@ -792,6 +792,7 @@ "url": { "type": "string", "format": "uri", + "contentMediaType": "application/octet-stream", "title": "URL path of the real file", "description": "URL path of the real file." } @@ -810,7 +811,8 @@ "type": "string", "format": "uuid", "title": "Primary Key", - "description": "Primary Key." + "description": "Primary Key.", + "x-special-description": "Sommetimes made by database automatically." }, "writer": { "type": "string", diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 1230bd9db..d11d864f1 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/migrate", - "version": "0.12.0", + "version": "0.12.1", "description": "Migration program from swagger to NestJS", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -63,8 +63,8 @@ "openapi-types": "^12.1.3", "prettier": "^3.2.5", "tstl": "^3.0.0", - "typescript": "^5.4.2", - "typia": "^5.5.7" + "typescript": "^5.4.3", + "typia": "^5.5.10" }, "files": [ "lib", diff --git a/packages/migrate/src/factories/TypeLiteralFactory.ts b/packages/migrate/src/factories/TypeLiteralFactory.ts new file mode 100644 index 000000000..fd502dc39 --- /dev/null +++ b/packages/migrate/src/factories/TypeLiteralFactory.ts @@ -0,0 +1,50 @@ +import ts from "typescript"; +import { Escaper } from "typia/lib/utils/Escaper"; + +export namespace TypeLiteralFactory { + export const generate = (value: any): ts.TypeNode => + typeof value === "boolean" + ? generateBoolean(value) + : typeof value === "number" + ? generateNumber(value) + : typeof value === "string" + ? generatestring(value) + : typeof value === "object" + ? value === null + ? generateNull() + : Array.isArray(value) + ? generateTuple(value) + : generateObject(value) + : ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); + + const generatestring = (str: string) => + ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(str)); + + const generateNumber = (num: number) => + ts.factory.createLiteralTypeNode(ts.factory.createNumericLiteral(num)); + + const generateBoolean = (bool: boolean) => + ts.factory.createLiteralTypeNode( + bool ? ts.factory.createTrue() : ts.factory.createFalse(), + ); + + const generateNull = () => + ts.factory.createLiteralTypeNode(ts.factory.createNull()); + + const generateTuple = (items: any[]) => + ts.factory.createTupleTypeNode(items.map(generate)); + + const generateObject = (obj: object) => + ts.factory.createTypeLiteralNode( + Object.entries(obj).map(([key, value]) => + ts.factory.createPropertySignature( + undefined, + Escaper.variable(key) + ? ts.factory.createIdentifier(key) + : ts.factory.createStringLiteral(key), + undefined, + generate(value), + ), + ), + ); +} diff --git a/packages/migrate/src/programmers/MigrateImportProgrammer.ts b/packages/migrate/src/programmers/MigrateImportProgrammer.ts index d8f6e1356..3fba92e2b 100644 --- a/packages/migrate/src/programmers/MigrateImportProgrammer.ts +++ b/packages/migrate/src/programmers/MigrateImportProgrammer.ts @@ -1,6 +1,6 @@ import ts from "typescript"; -import { ExpressionFactory } from "typia/lib/factories/ExpressionFactory"; +import { TypeLiteralFactory } from "../factories/TypeLiteralFactory"; import { FilePrinter } from "../utils/FilePrinter"; import { MapUtil } from "../utils/MapUtil"; @@ -38,18 +38,14 @@ export class MigrateImportProgrammer { ); } - public tag(type: string, arg: number | string): ts.TypeReferenceNode { + public tag(type: string, arg: any): ts.TypeReferenceNode { const instance: string = this.external({ type: "instance", library: "typia", name: "tags", }); return ts.factory.createTypeReferenceNode(`${instance}.${type}`, [ - ts.factory.createLiteralTypeNode( - typeof arg === "string" - ? ts.factory.createStringLiteral(arg) - : ExpressionFactory.number(arg), - ), + TypeLiteralFactory.generate(arg), ]); } diff --git a/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts b/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts index c192de7fa..8c42649d2 100644 --- a/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts +++ b/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts @@ -1,4 +1,5 @@ import ts from "typescript"; +import typia from "typia"; import { ExpressionFactory } from "typia/lib/factories/ExpressionFactory"; import { TypeFactory } from "typia/lib/factories/TypeFactory"; import { FormatCheatSheet } from "typia/lib/tags/internal/FormatCheatSheet"; @@ -27,7 +28,8 @@ export namespace MigrateSchemaProgrammer { const type: ts.TypeNode = (() => { // ATOMIC - if (SwaggerTypeChecker.isBoolean(schema)) return writeBoolean(schema); + if (SwaggerTypeChecker.isBoolean(schema)) + return writeBoolean(importer)(schema); else if (SwaggerTypeChecker.isInteger(schema)) return writeInteger(importer)(schema); else if (SwaggerTypeChecker.isNumber(schema)) @@ -58,13 +60,23 @@ export namespace MigrateSchemaProgrammer { /* ----------------------------------------------------------- ATOMICS ----------------------------------------------------------- */ - const writeBoolean = (schema: ISwaggerSchema.IBoolean): ts.TypeNode => { - if (schema.enum?.length) - return ts.factory.createLiteralTypeNode( - schema.enum[0] ? ts.factory.createTrue() : ts.factory.createFalse(), - ); - return TypeFactory.keyword("boolean"); - }; + const writeBoolean = + (importer: MigrateImportProgrammer) => + (schema: ISwaggerSchema.IBoolean): ts.TypeNode => { + if (schema.enum?.length) + return ts.factory.createLiteralTypeNode( + schema.enum[0] ? ts.factory.createTrue() : ts.factory.createFalse(), + ); + const intersection: ts.TypeNode[] = [TypeFactory.keyword("boolean")]; + writePlugin({ + importer, + regular: typia.misc.literals(), + intersection, + })(schema); + return intersection.length === 1 + ? intersection[0] + : ts.factory.createIntersectionTypeNode(intersection); + }; const writeInteger = (importer: MigrateImportProgrammer) => @@ -108,7 +120,11 @@ export namespace MigrateSchemaProgrammer { ); if (schema.multipleOf !== undefined) intersection.push(importer.tag("MultipleOf", schema.multipleOf)); - + writePlugin({ + importer, + regular: typia.misc.literals(), + intersection, + })(schema); return intersection.length === 1 ? intersection[0] : ts.factory.createIntersectionTypeNode(intersection); @@ -135,6 +151,15 @@ export namespace MigrateSchemaProgrammer { undefined ) intersection.push(importer.tag("Format", schema.format)); + if (schema.contentMediaType !== undefined) + intersection.push( + importer.tag("ContentMediaType", schema.contentMediaType), + ); + writePlugin({ + importer, + regular: typia.misc.literals(), + intersection, + })(schema); return intersection.length === 1 ? intersection[0] : ts.factory.createIntersectionTypeNode(intersection); @@ -156,6 +181,11 @@ export namespace MigrateSchemaProgrammer { intersection.push(importer.tag("MinItems", schema.minItems)); if (schema.maxItems !== undefined) intersection.push(importer.tag("MaxItems", schema.maxItems)); + writePlugin({ + importer, + regular: typia.misc.literals(), + intersection, + })(schema); return intersection.length === 1 ? intersection[0] : ts.factory.createIntersectionTypeNode(intersection); @@ -261,3 +291,17 @@ const writeComment = (schema: ISwaggerSchema): string => ...(schema.title !== undefined ? [`@title ${schema.title}`] : []), ...(schema.deprecated === true ? [`@deprecated`] : []), ].join("\n"); +const writePlugin = + (props: { + importer: MigrateImportProgrammer; + regular: string[]; + intersection: ts.TypeNode[]; + }) => + (schema: any) => { + const extra: any = {}; + for (const [key, value] of Object.entries(schema)) + if (value !== undefined && false === props.regular.includes(key)) + extra[key] = value; + if (Object.keys(extra).length !== 0) + props.intersection.push(props.importer.tag("JsonSchemaPlugin", extra)); + }; diff --git a/packages/migrate/src/structures/ISwaggerSchema.ts b/packages/migrate/src/structures/ISwaggerSchema.ts index d6b635864..b03000483 100644 --- a/packages/migrate/src/structures/ISwaggerSchema.ts +++ b/packages/migrate/src/structures/ISwaggerSchema.ts @@ -52,6 +52,7 @@ export namespace ISwaggerSchema { enum?: string[]; format?: string; pattern?: string; + contentMediaType?: string; /** @type uint */ minLength?: number; /** @type uint */ maxLength?: number; } diff --git a/website/package.json b/website/package.json index db10ebebc..6022e2020 100644 --- a/website/package.json +++ b/website/package.json @@ -23,7 +23,7 @@ "@mui/icons-material": "5.15.6", "@mui/material": "5.15.6", "@mui/system": "5.15.6", - "@nestia/migrate": "^0.12.0", + "@nestia/migrate": "^0.12.1", "@stackblitz/sdk": "^1.9.0", "js-yaml": "^4.1.0", "next": "14.1.0", @@ -33,7 +33,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-mui-fileuploader": "^0.5.2", - "typia": "^5.5.7" + "typia": "^5.5.10" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0",