From 60dcf12e78c31dcd7a021892b5c5004767235477 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Thu, 22 Feb 2024 22:02:57 +0900 Subject: [PATCH] Special case handling of `@nestia/migrate`. 1. Detailed type validation error report 2. Special handling of empty reference schema `$ref := ""` case --- .github/workflows/build.yml | 10 +++- packages/migrate/package.json | 4 +- packages/migrate/src/MigrateApplication.ts | 15 ++++-- .../src/analyzers/MigrateMethodAnalyzer.ts | 33 ++++++------ .../migrate/src/internal/MigrateCommander.ts | 12 ++++- .../programmers/MigrateSchemaProgrammer.ts | 12 +++-- packages/migrate/src/test/index.ts | 10 +++- website/package.json | 2 +- website/src/movies/editor/EditorMovie.tsx | 52 ++++++++++++------- 9 files changed, 99 insertions(+), 51 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64c60e787..1434bb69a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,13 @@ name: build -on: [push, pull_request] +on: + push: + paths: + - 'src/**' + - 'test/**' + pull_request: + paths: + - 'src/**' + - 'test/**' jobs: Ubuntu: diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 8dbfeede7..9efa29287 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/migrate", - "version": "0.8.2", + "version": "0.9.0", "description": "Migration program from swagger to NestJS", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -60,7 +60,7 @@ "prettier": "^3.2.5", "tstl": "^2.5.13", "typescript": "^5.3.3", - "typia": "^5.4.9" + "typia": "^5.4.11" }, "files": [ "lib", diff --git a/packages/migrate/src/MigrateApplication.ts b/packages/migrate/src/MigrateApplication.ts index e7109e1f2..1c0ce2181 100644 --- a/packages/migrate/src/MigrateApplication.ts +++ b/packages/migrate/src/MigrateApplication.ts @@ -1,4 +1,4 @@ -import typia from "typia"; +import typia, { IValidation } from "typia"; import { MigrateAnalyzer } from "./analyzers/MigrateAnalyzer"; import { NEST_TEMPLATE } from "./bundles/NEST_TEMPLATE"; @@ -12,10 +12,17 @@ import { IMigrateProgram } from "./structures/IMigrateProgram"; import { ISwagger } from "./structures/ISwagger"; export class MigrateApplication { - public readonly swagger: ISwagger; + private constructor(public readonly swagger: ISwagger) {} - public constructor(swagger: ISwagger) { - this.swagger = typia.assert(swagger); + public static create(swagger: ISwagger): IValidation { + const result = typia.validate(swagger); + if (result.success) + return { + success: true, + data: new MigrateApplication(swagger), + errors: [], + }; + return result; } public nest(config: MigrateApplication.IConfig): MigrateApplication.IOutput { diff --git a/packages/migrate/src/analyzers/MigrateMethodAnalyzer.ts b/packages/migrate/src/analyzers/MigrateMethodAnalyzer.ts index fec1c6fc3..2cbe78c90 100644 --- a/packages/migrate/src/analyzers/MigrateMethodAnalyzer.ts +++ b/packages/migrate/src/analyzers/MigrateMethodAnalyzer.ts @@ -33,13 +33,6 @@ export namespace MigrateMethodAnalzyer { failures.push( `does not support ${endpoint.method.toUpperCase()} method.`, ); - if (failures.length) { - console.log( - `Failed to migrate ${endpoint.method.toUpperCase()} ${endpoint.path}`, - ...failures.map((f) => ` - ${f}`), - ); - return null; - } const [headers, query] = ["header", "query"].map((type) => { const parameters = (route.parameters ?? []).filter( @@ -70,12 +63,12 @@ export namespace MigrateMethodAnalzyer { SwaggerSwaggerChecker.isArray(p.schema), ); if (objects.length === 1 && primitives.length === 0) return objects[0]; - else if (objects.length > 1) - throw new Error( - `Error on nestia.migrate.RouteProgrammer.analze(): ${type} typed parameters must be only one object type - ${StringUtil.capitalize( - endpoint.method, - )} "${endpoint.path}".`, + else if (objects.length > 1) { + failures.push( + `${type} typed parameters must be only one object type`, ); + return false; + } const dto: ISwaggerSchema.IObject | null = objects[0] ? SwaggerSwaggerChecker.isObject(objects[0]) @@ -156,11 +149,15 @@ export namespace MigrateMethodAnalzyer { if ( parameterNames.length !== (route.parameters ?? []).filter((p) => p.in === "path").length - ) { + ) + failures.push( + "number of path parameters are not matched with its full path.", + ); + + if (failures.length) { console.log( - `Failed to migrate ${endpoint.method.toUpperCase()} ${ - endpoint.path - }: number of path parameters are not matched with its full path.`, + `Failed to migrate ${endpoint.method.toUpperCase()} ${endpoint.path}`, + ...failures.map((f) => ` - ${f}`), ); return null; } @@ -201,8 +198,8 @@ export namespace MigrateMethodAnalzyer { schema: query, } : null, - body: body as IMigrateRoute.IBody, - success: success as IMigrateRoute.IBody, + body: body as IMigrateRoute.IBody | null, + success: success as IMigrateRoute.IBody | null, exceptions: Object.fromEntries( Object.entries(route.responses ?? {}) .filter( diff --git a/packages/migrate/src/internal/MigrateCommander.ts b/packages/migrate/src/internal/MigrateCommander.ts index 8542dad99..5d012ee1f 100644 --- a/packages/migrate/src/internal/MigrateCommander.ts +++ b/packages/migrate/src/internal/MigrateCommander.ts @@ -1,6 +1,7 @@ import fs from "fs"; import path from "path"; import { format } from "prettier"; +import { IValidation } from "typia"; import { MigrateApplication } from "../MigrateApplication"; import { MigrateFileArchiver } from "../archivers/MigrateFileArchiver"; @@ -33,7 +34,16 @@ export namespace MigrateCommander { return swagger; })(); - const app: MigrateApplication = new MigrateApplication(swagger); + const result: IValidation = + MigrateApplication.create(swagger); + if (result.success === false) { + console.log(result.errors); + throw new Error( + `Invalid swagger file (must follow the OpenAPI 3.0 spec).`, + ); + } + + const app: MigrateApplication = result.data; const { files } = options.mode === "nest" ? app.nest(options) : app.sdk(options); await MigrateFileArchiver.archive({ diff --git a/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts b/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts index 41f8e3cfb..0eb78a7e8 100644 --- a/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts +++ b/packages/migrate/src/programmers/MigrateSchemaProgrammer.ts @@ -4,8 +4,8 @@ import { TypeFactory } from "typia/lib/factories/TypeFactory"; import { FormatCheatSheet } from "typia/lib/tags/internal/FormatCheatSheet"; import { Escaper } from "typia/lib/utils/Escaper"; -import { ISwaggerSchema } from "../structures/ISwaggerSchema"; import { ISwaggerComponents } from "../structures/ISwaggerComponents"; +import { ISwaggerSchema } from "../structures/ISwaggerSchema"; import { FilePrinter } from "../utils/FilePrinter"; import { SwaggerSwaggerChecker } from "../utils/SwaggerTypeChecker"; import { MigrateImportProgrammer } from "./MigrateImportProgrammer"; @@ -232,8 +232,14 @@ export namespace MigrateSchemaProgrammer { const writeReference = (importer: MigrateImportProgrammer) => - (schema: ISwaggerSchema.IReference): ts.TypeReferenceNode => - importer.dto(schema.$ref.split("/").at(-1)!); + ( + schema: ISwaggerSchema.IReference, + ): ts.TypeReferenceNode | ts.KeywordTypeNode => { + const name: string = schema.$ref.split("/").at(-1)!; + return name === "" + ? TypeFactory.keyword("any") + : importer.dto(schema.$ref.split("/").at(-1)!); + }; /* ----------------------------------------------------------- UNIONS diff --git a/packages/migrate/src/test/index.ts b/packages/migrate/src/test/index.ts index e97d412aa..58442525c 100644 --- a/packages/migrate/src/test/index.ts +++ b/packages/migrate/src/test/index.ts @@ -1,6 +1,7 @@ import cp from "child_process"; import fs from "fs"; import { format } from "prettier"; +import { IValidation } from "typia"; import { MigrateApplication } from "../MigrateApplication"; import { MigrateFileArchiver } from "../archivers/MigrateFileArchiver"; @@ -36,7 +37,14 @@ const execute = measure(`${project}-${config.mode}-${config.simulate}-${config.e2e}`)( async () => { const directory = `${OUTPUT}/${project}-${config.mode}-${config.simulate}-${config.e2e}`; - const app: MigrateApplication = new MigrateApplication(swagger); + const result: IValidation = + MigrateApplication.create(swagger); + if (result.success === false) + throw new Error( + `Invalid swagger file (must follow the OpenAPI 3.0 spec).`, + ); + + const app: MigrateApplication = result.data; const { files } = config.mode === "nest" ? app.nest(config) : app.sdk(config); diff --git a/website/package.json b/website/package.json index 84f02cebc..f5176e89c 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.8.2", + "@nestia/migrate": "^0.9.0", "@stackblitz/sdk": "^1.9.0", "next": "14.1.0", "nextra": "latest", diff --git a/website/src/movies/editor/EditorMovie.tsx b/website/src/movies/editor/EditorMovie.tsx index 9443142ac..c9e5fcab1 100644 --- a/website/src/movies/editor/EditorMovie.tsx +++ b/website/src/movies/editor/EditorMovie.tsx @@ -12,28 +12,10 @@ import prettierEsTreePlugin from "prettier/plugins/estree"; import prettierTsPlugin from "prettier/plugins/typescript"; import { format } from "prettier/standalone"; import { useState } from "react"; +import { IValidation } from "typia"; import EditorUploader from "../../components/editor/EditorUploader"; -const createFiles = async ( - swagger: ISwagger, - config: MigrateApplication.IConfig, -): Promise => { - try { - const app: MigrateApplication = new MigrateApplication(swagger); - const { files }: MigrateApplication.IOutput = app.sdk(config); - for (const f of files) - if (f.file.substring(f.file.length - 3) === ".ts") - f.content = await format(f.content, { - parser: "typescript", - plugins: [prettierEsTreePlugin, prettierTsPlugin], - }); - return files; - } catch { - return null; - } -}; - const EditorMovie = () => { const [swagger, setSwagger] = useState(null); const [error, setError] = useState(null); @@ -41,6 +23,36 @@ const EditorMovie = () => { const [e2e, setE2e] = useState(true); const [progress, setProgress] = useState(false); + const createFiles = async ( + swagger: ISwagger, + config: MigrateApplication.IConfig, + ): Promise => { + try { + const result: IValidation = + MigrateApplication.create(swagger); + if (result.success === false) { + alert( + "Invalid swagger file (must follow OpenAPI 3.0 spec).\n\n" + + JSON.stringify(result.errors, null, 2), + ); + return null; + } + + const app: MigrateApplication = result.data; + const { files }: MigrateApplication.IOutput = app.sdk(config); + for (const f of files) + if (f.file.substring(f.file.length - 3) === ".ts") + f.content = await format(f.content, { + parser: "typescript", + plugins: [prettierEsTreePlugin, prettierTsPlugin], + }); + return files; + } catch (exp) { + alert(exp instanceof Error ? exp.message : "unkown error"); + return null; + } + }; + const handleSwagger = (swagger: ISwagger | null, error: string | null) => { setSwagger(swagger); setError(error); @@ -56,7 +68,7 @@ const EditorMovie = () => { }); if (files === null) { setProgress(false); - return alert("Invalid swagger file (must follow OpenAPI 3.0 spec)."); + return; } sdk.openProject(