diff --git a/packages/sdk/src/generates/SwaggerGenerator.ts b/packages/sdk/src/generates/SwaggerGenerator.ts index 82705d9d9..cb58c7e1c 100644 --- a/packages/sdk/src/generates/SwaggerGenerator.ts +++ b/packages/sdk/src/generates/SwaggerGenerator.ts @@ -4,7 +4,7 @@ import path from "path"; import { Singleton } from "tstl/thread/Singleton"; import ts from "typescript"; -import typia, { IJsonApplication } from "typia"; +import typia, { IJsonApplication, IJsonComponents } from "typia"; import { MetadataCollection } from "typia/lib/factories/MetadataCollection"; import { JsonApplicationProgrammer } from "typia/lib/programmers/json/JsonApplicationProgrammer"; @@ -13,8 +13,9 @@ import { IRoute } from "../structures/IRoute"; import { ISwagger } from "../structures/ISwagger"; import { ISwaggerError } from "../structures/ISwaggerError"; import { ISwaggerInfo } from "../structures/ISwaggerInfo"; +import { ISwaggerLazyProperty } from "../structures/ISwaggerLazyProperty"; +import { ISwaggerLazySchema } from "../structures/ISwaggerLazySchema"; import { ISwaggerRoute } from "../structures/ISwaggerRoute"; -import { ISwaggerSchemaTuple } from "../structures/ISwaggerSchemaTuple"; import { ISwaggerSecurityScheme } from "../structures/ISwaggerSecurityScheme"; import { FileRetriever } from "../utils/FileRetriever"; import { MapUtil } from "../utils/MapUtil"; @@ -25,7 +26,8 @@ export namespace SwaggerGenerator { config: INestiaConfig.ISwaggerConfig; checker: ts.TypeChecker; collection: MetadataCollection; - tuples: Array; + lazySchemas: Array; + lazyProperties: Array; errors: ISwaggerError[]; } @@ -63,7 +65,8 @@ export namespace SwaggerGenerator { // CONSTRUCT SWAGGER DOCUMENTS const errors: ISwaggerError[] = []; - const tuples: Array = []; + const lazySchemas: Array = []; + const lazyProperties: Array = []; const swagger: ISwagger = await initialize(config); const pathDict: Map< string, @@ -83,27 +86,35 @@ export namespace SwaggerGenerator { config, checker, collection, - tuples, + lazySchemas, + lazyProperties, errors, })(route); } swagger.paths = {}; - for (const [path, routes] of pathDict) { - swagger.paths[path] = routes; - } + for (const [path, routes] of pathDict) swagger.paths[path] = routes; // FILL JSON-SCHEMAS const application: IJsonApplication = JsonApplicationProgrammer.write({ purpose: "swagger", - })(tuples.map(({ metadata }) => metadata)); + })(lazySchemas.map(({ metadata }) => metadata)); swagger.components = { ...(swagger.components ?? {}), ...(application.components ?? {}), }; - tuples.forEach(({ schema }, index) => { + lazySchemas.forEach(({ schema }, index) => { Object.assign(schema, application.schemas[index]!); }); + for (const p of lazyProperties) + Object.assign( + p.schema, + ( + application.components.schemas?.[ + p.object + ] as IJsonComponents.IObject + )?.properties[p.property], + ); // CONFIGURE SECURITY if (config.security) fill_security(config.security, swagger); diff --git a/packages/sdk/src/generates/internal/SwaggerSchemaGenerator.ts b/packages/sdk/src/generates/internal/SwaggerSchemaGenerator.ts index bd5dd7313..d75abe396 100644 --- a/packages/sdk/src/generates/internal/SwaggerSchemaGenerator.ts +++ b/packages/sdk/src/generates/internal/SwaggerSchemaGenerator.ts @@ -10,8 +10,9 @@ import { ValidationPipe } from "typia/lib/typings/ValidationPipe"; import { INestiaConfig } from "../../INestiaConfig"; import { IRoute } from "../../structures/IRoute"; import { ISwaggerError } from "../../structures/ISwaggerError"; +import { ISwaggerLazyProperty } from "../../structures/ISwaggerLazyProperty"; +import { ISwaggerLazySchema } from "../../structures/ISwaggerLazySchema"; import { ISwaggerRoute } from "../../structures/ISwaggerRoute"; -import { ISwaggerSchemaTuple } from "../../structures/ISwaggerSchemaTuple"; import { SwaggerSchemaValidator } from "./SwaggerSchemaValidator"; export namespace SwaggerSchemaGenerator { @@ -19,7 +20,8 @@ export namespace SwaggerSchemaGenerator { config: INestiaConfig.ISwaggerConfig; checker: ts.TypeChecker; collection: MetadataCollection; - tuples: Array; + lazySchemas: Array; + lazyProperties: Array; errors: ISwaggerError[]; } @@ -286,6 +288,10 @@ export namespace SwaggerSchemaGenerator { ( result: ValidationPipe, ): ISwaggerRoute.IParameter[] => { + const decoded: ISwaggerRoute.IParameter = lazy(props)(route)( + param, + result, + ); if (result.success === false) { props.errors.push( ...result.errors.map((e) => ({ @@ -294,29 +300,35 @@ export namespace SwaggerSchemaGenerator { from: param.name, })), ); - return [lazy(props)(route)(param, result)]; + return [decoded]; } else if ( props.config.decompose !== true || result.data.objects.length === 0 ) - return [lazy(props)(route)(param, result)]; + return [decoded]; - return result.data.objects[0].properties.map((p) => { - const schema = coalesce(props)({ - success: true, - data: p.value, + return result.data.objects[0].properties + .filter((p) => + p.jsDocTags.every((tag) => tag.name !== "hidden"), + ) + .map((p) => { + const schema: IJsonSchema = {}; + props.lazyProperties.push({ + schema, + object: result.data.objects[0].name, + property: p.key.constants[0].values[0] as string, + }); + return { + name: p.key.constants[0].values[0] as string, + in: + param.category === "headers" + ? "header" + : param.category, + schema, + description: p.description ?? undefined, + required: p.value.isRequired(), + }; }); - return { - name: p.key.constants[0].values[0] as string, - in: - param.category === "headers" - ? "header" - : param.category, - schema, - description: p.description ?? undefined, - required: p.value.isRequired(), - }; - }); }; const lazy = @@ -347,7 +359,7 @@ export namespace SwaggerSchemaGenerator { result: ValidationPipe, ): IJsonSchema => { const schema: IJsonSchema = {} as any; - props.tuples.push({ + props.lazySchemas.push({ metadata: result.success ? result.data : any.get(), schema, }); diff --git a/packages/sdk/src/structures/ISwaggerLazyProperty.ts b/packages/sdk/src/structures/ISwaggerLazyProperty.ts new file mode 100644 index 000000000..20bea1ebb --- /dev/null +++ b/packages/sdk/src/structures/ISwaggerLazyProperty.ts @@ -0,0 +1,7 @@ +import { IJsonSchema } from "typia"; + +export interface ISwaggerLazyProperty { + schema: IJsonSchema; + object: string; + property: string; +} diff --git a/packages/sdk/src/structures/ISwaggerSchemaTuple.ts b/packages/sdk/src/structures/ISwaggerLazySchema.ts similarity index 79% rename from packages/sdk/src/structures/ISwaggerSchemaTuple.ts rename to packages/sdk/src/structures/ISwaggerLazySchema.ts index f24580991..5d52c1cc8 100644 --- a/packages/sdk/src/structures/ISwaggerSchemaTuple.ts +++ b/packages/sdk/src/structures/ISwaggerLazySchema.ts @@ -1,7 +1,7 @@ import { IJsonSchema } from "typia"; import { Metadata } from "typia/lib/schemas/metadata/Metadata"; -export interface ISwaggerSchemaTuple { +export interface ISwaggerLazySchema { metadata: Metadata; schema: IJsonSchema; } diff --git a/test/features/headers-decompose/swagger.json b/test/features/headers-decompose/swagger.json index 2957a2928..e54f31889 100644 --- a/test/features/headers-decompose/swagger.json +++ b/test/features/headers-decompose/swagger.json @@ -23,6 +23,8 @@ "name": "x-category", "in": "header", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string", "enum": [ "x", @@ -36,6 +38,8 @@ "name": "x-memo", "in": "header", "schema": { + "x-typia-required": false, + "x-typia-optional": true, "type": "string" }, "required": false @@ -44,7 +48,21 @@ "name": "x-name", "in": "header", "schema": { - "type": "string" + "x-typia-jsDocTags": [ + { + "name": "default", + "text": [ + { + "text": "Samchon", + "kind": "text" + } + ] + } + ], + "x-typia-required": false, + "x-typia-optional": true, + "type": "string", + "default": "Samchon" }, "required": false }, @@ -52,8 +70,12 @@ "name": "x-values", "in": "header", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "array", "items": { + "x-typia-required": true, + "x-typia-optional": false, "type": "number" } }, @@ -63,24 +85,17 @@ "name": "x-flags", "in": "header", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "array", "items": { + "x-typia-required": true, + "x-typia-optional": false, "type": "boolean" } }, "required": true }, - { - "name": "X-descriptions", - "in": "header", - "schema": { - "type": "array", - "items": { - "type": "string" - } - }, - "required": true - }, { "name": "section", "in": "path", @@ -115,6 +130,8 @@ "name": "x-category", "in": "header", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string", "enum": [ "x", @@ -128,6 +145,8 @@ "name": "x-memo", "in": "header", "schema": { + "x-typia-required": false, + "x-typia-optional": true, "type": "string" }, "required": false @@ -136,7 +155,21 @@ "name": "x-name", "in": "header", "schema": { - "type": "string" + "x-typia-jsDocTags": [ + { + "name": "default", + "text": [ + { + "text": "Samchon", + "kind": "text" + } + ] + } + ], + "x-typia-required": false, + "x-typia-optional": true, + "type": "string", + "default": "Samchon" }, "required": false }, @@ -144,8 +177,12 @@ "name": "x-values", "in": "header", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "array", "items": { + "x-typia-required": true, + "x-typia-optional": false, "type": "number" } }, @@ -155,24 +192,17 @@ "name": "x-flags", "in": "header", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "array", "items": { + "x-typia-required": true, + "x-typia-optional": false, "type": "boolean" } }, "required": true }, - { - "name": "X-descriptions", - "in": "header", - "schema": { - "type": "array", - "items": { - "type": "string" - } - }, - "required": true - }, { "name": "section", "in": "path", diff --git a/test/features/query-decompose/swagger.json b/test/features/query-decompose/swagger.json index 6db01dd2d..2cf072fad 100644 --- a/test/features/query-decompose/swagger.json +++ b/test/features/query-decompose/swagger.json @@ -23,6 +23,8 @@ "name": "limit", "in": "query", "schema": { + "x-typia-required": false, + "x-typia-optional": true, "type": "number" }, "required": false @@ -31,6 +33,8 @@ "name": "enforce", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "boolean" }, "required": true @@ -39,8 +43,12 @@ "name": "values", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "array", "items": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string" } }, @@ -50,6 +58,8 @@ "name": "atomic", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string", "nullable": true }, @@ -83,6 +93,8 @@ "in": "query", "schema": { "type": "string", + "x-typia-required": false, + "x-typia-optional": true, "pattern": "^([+-]?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)$" }, "required": false @@ -91,6 +103,8 @@ "name": "enforce", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string", "enum": [ "false", @@ -103,6 +117,8 @@ "name": "atomic", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string" }, "required": true @@ -111,8 +127,12 @@ "name": "values", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "array", "items": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string" } }, @@ -186,6 +206,8 @@ "name": "limit", "in": "query", "schema": { + "x-typia-required": false, + "x-typia-optional": true, "type": "number" }, "required": false @@ -194,6 +216,8 @@ "name": "enforce", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "boolean" }, "required": true @@ -202,8 +226,12 @@ "name": "values", "in": "query", "schema": { + "x-typia-required": true, + "x-typia-optional": false, "type": "array", "items": { + "x-typia-required": true, + "x-typia-optional": false, "type": "string" } }, @@ -268,6 +296,79 @@ "atomic" ], "x-typia-jsDocTags": [] + }, + "INestQuery": { + "type": "object", + "properties": { + "limit": { + "type": "string", + "x-typia-required": false, + "x-typia-optional": true, + "pattern": "^([+-]?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)$" + }, + "enforce": { + "x-typia-required": true, + "x-typia-optional": false, + "type": "string", + "enum": [ + "false", + "true" + ] + }, + "atomic": { + "x-typia-required": true, + "x-typia-optional": false, + "type": "string" + }, + "values": { + "x-typia-required": true, + "x-typia-optional": false, + "type": "array", + "items": { + "x-typia-required": true, + "x-typia-optional": false, + "type": "string" + } + } + }, + "nullable": false, + "required": [ + "enforce", + "atomic", + "values" + ], + "x-typia-jsDocTags": [] + }, + "Omit": { + "type": "object", + "properties": { + "limit": { + "x-typia-required": false, + "x-typia-optional": true, + "type": "number" + }, + "enforce": { + "x-typia-required": true, + "x-typia-optional": false, + "type": "boolean" + }, + "values": { + "x-typia-required": true, + "x-typia-optional": false, + "type": "array", + "items": { + "x-typia-required": true, + "x-typia-optional": false, + "type": "string" + } + } + }, + "nullable": false, + "required": [ + "enforce", + "values" + ], + "x-typia-jsDocTags": [] } }, "securitySchemes": {