From c78adbccdddce1cee829e7b4f9e34812ff937f36 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Mon, 11 Nov 2024 21:36:23 +0900 Subject: [PATCH] Fix `OpenApi.IJsonSchema.ITuple` downgrading problem. --- package.json | 2 +- src/converters/HttpLlmConverter.ts | 2 +- src/converters/LlmConverterV3.ts | 12 +----- src/converters/OpenApiV3Downgrader.ts | 30 ++++++------- src/converters/SwaggerV2Downgrader.ts | 43 +++++++++---------- src/utils/OpenApiTypeChecker.ts | 1 + .../test_json_schema_downgrade_v20_tuple.ts | 35 +++++++++++++++ .../test_json_schema_downgrade_v30_tuple.ts | 35 +++++++++++++++ 8 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 test/features/openapi/test_json_schema_downgrade_v20_tuple.ts create mode 100644 test/features/openapi/test_json_schema_downgrade_v30_tuple.ts diff --git a/package.json b/package.json index 5e3653f..ccad21e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samchon/openapi", - "version": "2.0.0-dev.20241111-3", + "version": "2.0.0-dev.20241112", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "main": "./lib/index.js", "module": "./lib/index.mjs", diff --git a/src/converters/HttpLlmConverter.ts b/src/converters/HttpLlmConverter.ts index 7c99789..c21d109 100644 --- a/src/converters/HttpLlmConverter.ts +++ b/src/converters/HttpLlmConverter.ts @@ -78,7 +78,7 @@ export namespace HttpLlmConverter { errors.push({ method: route.method, path: route.path, - messages: ["Failed to escape $ref"], + messages: ["Failed to escape $ref"], // @todo operation: () => route.operation(), route: () => route as any as Route, }); diff --git a/src/converters/LlmConverterV3.ts b/src/converters/LlmConverterV3.ts index 5067e17..895a1a8 100644 --- a/src/converters/LlmConverterV3.ts +++ b/src/converters/LlmConverterV3.ts @@ -16,18 +16,10 @@ export namespace LlmConverterV3 { recursive: props.recursive, }); if (resolved === null) return null; - const downgraded: ILlmSchemaV3 = OpenApiV3Downgrader.downgradeSchema({ + return OpenApiV3Downgrader.downgradeSchema({ original: {}, downgraded: {}, - })(resolved) as ILlmSchemaV3; - LlmTypeCheckerV3.visit(downgraded, (schema) => { - if ( - LlmTypeCheckerV3.isOneOf(schema) && - (schema as any).discriminator !== undefined - ) - delete (schema as any).discriminator; - }); - return downgraded; + })(resolved); }; export const separate = (props: { diff --git a/src/converters/OpenApiV3Downgrader.ts b/src/converters/OpenApiV3Downgrader.ts index fdb8697..3991901 100644 --- a/src/converters/OpenApiV3Downgrader.ts +++ b/src/converters/OpenApiV3Downgrader.ts @@ -208,31 +208,29 @@ export namespace OpenApiV3Downgrader { items: downgradeSchema(collection)(schema.items), }); else if (OpenApiTypeChecker.isTuple(schema)) - union.push({ + visit({ ...schema, - items: ((): OpenApiV3.IJsonSchema => { - if (schema.additionalItems === true) return {}; - const elements = [ + type: "array", + items: { + oneOf: [ ...schema.prefixItems, - ...(typeof schema.additionalItems === "object" - ? [downgradeSchema(collection)(schema.additionalItems)] + ...(typeof schema.additionalItems === "object" && + schema.additionalItems !== null + ? [schema.additionalItems] : []), - ]; - if (elements.length === 0) return {}; - return { - oneOf: elements.map(downgradeSchema(collection) as any), - }; - })(), - minItems: schema.prefixItems.length, + ], + }, + minItems: schema.minItems ?? schema.prefixItems.length, maxItems: - !!schema.additionalItems === true + schema.maxItems ?? + (schema.additionalItems === true ? undefined - : schema.prefixItems.length, + : schema.prefixItems.length), ...{ prefixItems: undefined, additionalItems: undefined, }, - }); + } satisfies OpenApi.IJsonSchema.IArray); else if (OpenApiTypeChecker.isObject(schema)) union.push({ ...schema, diff --git a/src/converters/SwaggerV2Downgrader.ts b/src/converters/SwaggerV2Downgrader.ts index 2dd08b5..ee4a380 100644 --- a/src/converters/SwaggerV2Downgrader.ts +++ b/src/converters/SwaggerV2Downgrader.ts @@ -228,34 +228,29 @@ export namespace SwaggerV2Downgrader { : undefined, }); else if (OpenApiTypeChecker.isTuple(schema)) - union.push({ + visit({ ...schema, - items: ((): SwaggerV2.IJsonSchema => { - if (schema.additionalItems === true) return {}; - const elements = [ + type: "array", + items: { + oneOf: [ ...schema.prefixItems, - ...(typeof schema.additionalItems === "object" - ? [downgradeSchema(collection)(schema.additionalItems)] + ...(typeof schema.additionalItems === "object" && + schema.additionalItems !== null + ? [schema.additionalItems] : []), - ]; - if (elements.length === 0) return {}; - return { - "x-oneOf": elements.map(downgradeSchema(collection) as any), - }; - })(), - minItems: schema.prefixItems.length, + ], + }, + minItems: schema.minItems ?? schema.prefixItems.length, maxItems: - !!schema.additionalItems === true + schema.maxItems ?? + (schema.additionalItems === true ? undefined - : schema.prefixItems.length, + : schema.prefixItems.length), ...{ prefixItems: undefined, additionalItems: undefined, }, - examples: schema.examples - ? Object.values(schema.examples) - : undefined, - }); + } satisfies OpenApi.IJsonSchema.IArray); else if (OpenApiTypeChecker.isObject(schema)) union.push({ ...schema, @@ -285,16 +280,18 @@ export namespace SwaggerV2Downgrader { const insert = (value: any): void => { const matched: SwaggerV2.IJsonSchema.INumber | undefined = union.find( (u) => - (u as SwaggerV2.IJsonSchema.__ISignificant).type === value, + (u as SwaggerV2.IJsonSchema.__ISignificant).type === + typeof value, ) as SwaggerV2.IJsonSchema.INumber | undefined; if (matched !== undefined) { matched.enum ??= []; matched.enum.push(value); } else union.push({ type: typeof value as "number", enum: [value] }); - if (OpenApiTypeChecker.isConstant(schema)) insert(schema.const); - else if (OpenApiTypeChecker.isOneOf(schema)) - schema.oneOf.forEach(insert); }; + if (OpenApiTypeChecker.isConstant(schema)) insert(schema.const); + else if (OpenApiTypeChecker.isOneOf(schema)) + for (const u of schema.oneOf) + if (OpenApiTypeChecker.isConstant(u)) insert(u.const); }; visit(input); diff --git a/src/utils/OpenApiTypeChecker.ts b/src/utils/OpenApiTypeChecker.ts index 8194ad4..8154726 100644 --- a/src/utils/OpenApiTypeChecker.ts +++ b/src/utils/OpenApiTypeChecker.ts @@ -201,6 +201,7 @@ export namespace OpenApiTypeChecker { return { ...props, oneOf: filtered.map((v) => flat(props.components, v)).flat(), + discriminator: undefined, }; } else if (isObject(props.schema)) { // OBJECT diff --git a/test/features/openapi/test_json_schema_downgrade_v20_tuple.ts b/test/features/openapi/test_json_schema_downgrade_v20_tuple.ts new file mode 100644 index 0000000..6d945ba --- /dev/null +++ b/test/features/openapi/test_json_schema_downgrade_v20_tuple.ts @@ -0,0 +1,35 @@ +import { TestValidator } from "@nestia/e2e"; +import { SwaggerV2Downgrader } from "@samchon/openapi/lib/converters/SwaggerV2Downgrader"; +import typia from "typia"; + +export const test_json_schema_downgrade_v20_tuple = (): void => { + const collection = typia.json.application<[[false, 1, 2, "three", null]]>(); + const schema = SwaggerV2Downgrader.downgradeSchema({ + original: collection.components, + downgraded: {}, + })(collection.schemas[0]); + TestValidator.equals("tuple")(schema)({ + type: "array", + items: { + "x-oneOf": [ + { + type: "boolean", + enum: [false], + "x-nullable": true, + }, + { + type: "number", + enum: [1, 2], + "x-nullable": true, + }, + { + type: "string", + enum: ["three"], + "x-nullable": true, + }, + ], + }, + minItems: 5, + maxItems: 5, + }); +}; diff --git a/test/features/openapi/test_json_schema_downgrade_v30_tuple.ts b/test/features/openapi/test_json_schema_downgrade_v30_tuple.ts new file mode 100644 index 0000000..fc0bff9 --- /dev/null +++ b/test/features/openapi/test_json_schema_downgrade_v30_tuple.ts @@ -0,0 +1,35 @@ +import { TestValidator } from "@nestia/e2e"; +import { OpenApiV3Downgrader } from "@samchon/openapi/lib/converters/OpenApiV3Downgrader"; +import typia from "typia"; + +export const test_json_schema_downgrade_v30_tuple = (): void => { + const collection = typia.json.application<[[false, 1, 2, "three", null]]>(); + const schema = OpenApiV3Downgrader.downgradeSchema({ + original: collection.components, + downgraded: {}, + })(collection.schemas[0]); + TestValidator.equals("tuple")(schema)({ + type: "array", + items: { + oneOf: [ + { + type: "boolean", + enum: [false], + nullable: true, + }, + { + type: "number", + enum: [1, 2], + nullable: true, + }, + { + type: "string", + enum: ["three"], + nullable: true, + }, + ], + }, + minItems: 5, + maxItems: 5, + }); +};