From 0144ed944cff34da6a3cfd3140acbbc1b2a3d4de Mon Sep 17 00:00:00 2001 From: Romain Lenzotti Date: Sun, 3 Sep 2023 10:05:08 +0200 Subject: [PATCH] fix(schema): fix OASv3 schema generation fails when model used in @OneOf has kind property Closes: #2413 --- .../specs/schema/src/components/anyMapper.ts | 4 +- packages/specs/schema/src/domain/JsonMap.ts | 3 +- packages/specs/schema/src/domain/JsonMedia.ts | 2 +- .../specs/schema/src/domain/JsonOperation.ts | 2 +- .../src/domain/JsonOperationPathsMap.ts | 3 +- .../specs/schema/src/domain/JsonParameter.ts | 2 +- .../schema/src/domain/JsonRequestBody.ts | 2 +- .../specs/schema/src/domain/JsonResponse.ts | 2 +- .../specs/schema/src/domain/JsonSchema.ts | 7 +- .../schema/src/utils/inlineEnums.spec.ts | 6 +- .../specs/schema/src/utils/inlineEnums.ts | 2 +- .../discriminator.integration.spec.ts.snap | 97 +++++++++++++++++++ .../discriminator.integration.spec.ts | 50 ++++++++++ tsconfig.json | 5 +- 14 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 packages/specs/schema/test/integrations/__snapshots__/discriminator.integration.spec.ts.snap diff --git a/packages/specs/schema/src/components/anyMapper.ts b/packages/specs/schema/src/components/anyMapper.ts index 353b9e714ab..a0e67177c73 100644 --- a/packages/specs/schema/src/components/anyMapper.ts +++ b/packages/specs/schema/src/components/anyMapper.ts @@ -20,8 +20,8 @@ export function anyMapper(input: any, options: JsonSchemaOptions = {}): any { return toRef(enumSchema, enumSchema.toJSON(options), options); } - if (input.kind) { - const kind = oneOfMapper(input.kind, "map"); + if (input.$kind && input.$isJsonDocument) { + const kind = oneOfMapper(input.$kind, "map"); const schema = execMapper(kind, input, mapGenericsOptions(options)); return input.canRef ? toRef(input, schema, options) : schema; diff --git a/packages/specs/schema/src/domain/JsonMap.ts b/packages/specs/schema/src/domain/JsonMap.ts index 4ef4b7c0375..02b136dfb20 100644 --- a/packages/specs/schema/src/domain/JsonMap.ts +++ b/packages/specs/schema/src/domain/JsonMap.ts @@ -5,7 +5,8 @@ import {execMapper} from "../registries/JsonSchemaMapperContainer"; export class JsonMap extends Map { [key: string]: any; - kind: string = "map"; + $kind: string = "map"; + readonly $isJsonDocument = true; constructor(obj: Partial = {}) { super(); diff --git a/packages/specs/schema/src/domain/JsonMedia.ts b/packages/specs/schema/src/domain/JsonMedia.ts index 9eeffad18a4..4e3a3b23029 100644 --- a/packages/specs/schema/src/domain/JsonMedia.ts +++ b/packages/specs/schema/src/domain/JsonMedia.ts @@ -3,7 +3,7 @@ import {JsonMap} from "./JsonMap"; import {JsonSchema} from "./JsonSchema"; export class JsonMedia extends JsonMap> { - kind: string = "operationMedia"; + $kind: string = "operationMedia"; groups: string[] = []; groupsName: string; diff --git a/packages/specs/schema/src/domain/JsonOperation.ts b/packages/specs/schema/src/domain/JsonOperation.ts index 71131093cfd..52e2019c044 100644 --- a/packages/specs/schema/src/domain/JsonOperation.ts +++ b/packages/specs/schema/src/domain/JsonOperation.ts @@ -20,7 +20,7 @@ export interface JsonOperationOptions extends OS3Operation { - kind: string = "operation"; + $kind: string = "operation"; readonly operationPaths: Map = new Map(); #status: number; diff --git a/packages/specs/schema/src/domain/JsonOperationPathsMap.ts b/packages/specs/schema/src/domain/JsonOperationPathsMap.ts index 0448edbcd3d..91238fd0bfe 100644 --- a/packages/specs/schema/src/domain/JsonOperationPathsMap.ts +++ b/packages/specs/schema/src/domain/JsonOperationPathsMap.ts @@ -2,7 +2,8 @@ import {OperationMethods} from "../constants/httpMethods"; import {JsonMethodPath} from "./JsonOperation"; export class JsonOperationPathsMap extends Map { - kind: string = "operationPaths"; + $kind: string = "operationPaths"; + readonly $isJsonDocument = true; setOperationPath(operationPath: JsonMethodPath) { if (operationPath.method !== OperationMethods.CUSTOM) { diff --git a/packages/specs/schema/src/domain/JsonParameter.ts b/packages/specs/schema/src/domain/JsonParameter.ts index 3fe3c59125b..a14c7ec9736 100644 --- a/packages/specs/schema/src/domain/JsonParameter.ts +++ b/packages/specs/schema/src/domain/JsonParameter.ts @@ -8,7 +8,7 @@ import {formatParameterType} from "./JsonParameterTypes"; import {JsonSchema} from "./JsonSchema"; export class JsonParameter extends JsonMap> implements NestedGenerics { - kind = "operationInParameter"; + $kind = "operationInParameter"; nestedGenerics: Type[][] = []; groups: string[]; diff --git a/packages/specs/schema/src/domain/JsonRequestBody.ts b/packages/specs/schema/src/domain/JsonRequestBody.ts index 2fd5db4fd8c..6b5067d9f33 100644 --- a/packages/specs/schema/src/domain/JsonRequestBody.ts +++ b/packages/specs/schema/src/domain/JsonRequestBody.ts @@ -6,7 +6,7 @@ import {JsonSchema} from "./JsonSchema"; export type JsonRequestBodyOptions = OS3RequestBody; export class JsonRequestBody extends JsonMap { - kind = "operationRequestBody"; + $kind = "operationRequestBody"; constructor(obj: Partial = {}) { super(obj); diff --git a/packages/specs/schema/src/domain/JsonResponse.ts b/packages/specs/schema/src/domain/JsonResponse.ts index 8f7fe393032..e459b51ebfb 100644 --- a/packages/specs/schema/src/domain/JsonResponse.ts +++ b/packages/specs/schema/src/domain/JsonResponse.ts @@ -9,7 +9,7 @@ import {JsonSchema} from "./JsonSchema"; export type JsonResponseOptions = OS3Response; export class JsonResponse extends JsonMap { - kind: string = "operationResponse"; + $kind: string = "operationResponse"; status: number; diff --git a/packages/specs/schema/src/domain/JsonSchema.ts b/packages/specs/schema/src/domain/JsonSchema.ts index ad2bb689518..aeccb4b0b55 100644 --- a/packages/specs/schema/src/domain/JsonSchema.ts +++ b/packages/specs/schema/src/domain/JsonSchema.ts @@ -55,7 +55,7 @@ function mapToJsonSchema(item: any): any { return (item as any[]).map(mapToJsonSchema); } - if (item.isStore || item.isJsonSchema || item.isLazyRef) { + if (item.isStore || item.$isJsonDocument || item.isLazyRef) { return item; } @@ -75,9 +75,8 @@ function mapToJsonSchema(item: any): any { } export class JsonSchema extends Map implements NestedGenerics { - kind: string = "schema"; - - readonly isJsonSchema = true; + readonly $kind: string = "schema"; + readonly $isJsonDocument = true; readonly $hooks = new Hooks(); readonly $required: Set = new Set(); readonly $allow: any[] = []; diff --git a/packages/specs/schema/src/utils/inlineEnums.spec.ts b/packages/specs/schema/src/utils/inlineEnums.spec.ts index 81974606c5c..3e94bc7ec25 100644 --- a/packages/specs/schema/src/utils/inlineEnums.spec.ts +++ b/packages/specs/schema/src/utils/inlineEnums.spec.ts @@ -5,7 +5,7 @@ describe("inlineEnums()", () => { const result = inlineEnums( { enum: { - isJsonSchema: true, + $isJsonDocument: true, toJSON() { return {enum: ["type"]}; } @@ -26,7 +26,7 @@ describe("inlineEnums()", () => { { type: "object", enum: { - isJsonSchema: true, + $isJsonDocument: true, toJSON() { return {enum: ["type"]}; } @@ -46,7 +46,7 @@ describe("inlineEnums()", () => { { type: "string", enum: { - isJsonSchema: true, + $isJsonDocument: true, toJSON() { return {enum: ["type"]}; } diff --git a/packages/specs/schema/src/utils/inlineEnums.ts b/packages/specs/schema/src/utils/inlineEnums.ts index 284610d6dc9..8f84d6760eb 100644 --- a/packages/specs/schema/src/utils/inlineEnums.ts +++ b/packages/specs/schema/src/utils/inlineEnums.ts @@ -2,7 +2,7 @@ import {JsonSchema} from "../domain/JsonSchema"; import {JsonSchemaOptions} from "../interfaces/JsonSchemaOptions"; export function inlineEnums(obj: any, schema: JsonSchema, options: JsonSchemaOptions) { - if (options.inlineEnums && obj.enum?.isJsonSchema) { + if (options.inlineEnums && obj.enum?.$isJsonDocument) { obj.enum = obj.enum.toJSON().enum; } diff --git a/packages/specs/schema/test/integrations/__snapshots__/discriminator.integration.spec.ts.snap b/packages/specs/schema/test/integrations/__snapshots__/discriminator.integration.spec.ts.snap new file mode 100644 index 00000000000..852c1ad6a18 --- /dev/null +++ b/packages/specs/schema/test/integrations/__snapshots__/discriminator.integration.spec.ts.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Discriminator with kind property should generate the spec 1`] = ` +Object { + "components": Object { + "schemas": Object { + "FirstImpl": Object { + "properties": Object { + "kind": Object { + "enum": Array [ + "json", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "one", + "two", + ], + "example": "one", + "type": "string", + }, + }, + "type": "object", + }, + "ParentModel": Object { + "properties": Object { + "test": Object { + "discriminator": Object { + "propertyName": "type", + }, + "oneOf": Array [ + Object { + "$ref": "#/components/schemas/FirstImpl", + }, + Object { + "$ref": "#/components/schemas/SecondImpl", + }, + ], + "required": Array [ + "type", + ], + }, + }, + "type": "object", + }, + "SecondImpl": Object { + "properties": Object { + "prop": Object { + "type": "string", + }, + "type": Object { + "enum": Array [ + "one", + "two", + ], + "example": "two", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "paths": Object { + "/test": Object { + "get": Object { + "operationId": "testGet", + "parameters": Array [], + "responses": Object { + "200": Object { + "content": Object { + "application/json": Object { + "schema": Object { + "items": Object { + "$ref": "#/components/schemas/ParentModel", + }, + "type": "array", + }, + }, + }, + "description": "Success", + }, + }, + "tags": Array [ + "Test", + ], + }, + }, + }, + "tags": Array [ + Object { + "name": "Test", + }, + ], +} +`; diff --git a/packages/specs/schema/test/integrations/discriminator.integration.spec.ts b/packages/specs/schema/test/integrations/discriminator.integration.spec.ts index 6f4c2cf19ce..ddd0c081032 100644 --- a/packages/specs/schema/test/integrations/discriminator.integration.spec.ts +++ b/packages/specs/schema/test/integrations/discriminator.integration.spec.ts @@ -1,12 +1,15 @@ import {Controller} from "@tsed/di"; import {BodyParams, PathParams} from "@tsed/platform-params"; +import * as Path from "path"; import { DiscriminatorKey, DiscriminatorValue, + Enum, Get, getJsonSchema, getSpec, JsonEntityStore, + Name, OneOf, Partial, Patch, @@ -1287,4 +1290,51 @@ describe("Discriminator", () => { expect(JsonEntityStore.from(Event).isDiscriminatorChild).toEqual(false); }); }); + describe("with kind property", () => { + it("should generate the spec", () => { + enum Discriminator { + ONE = "one", + TWO = "two" + } + + abstract class BaseModel { + @Enum(Discriminator.ONE, Discriminator.TWO) + @DiscriminatorKey() + public type!: Discriminator.ONE | Discriminator.TWO; + } + + @DiscriminatorValue(Discriminator.ONE) + class FirstImpl extends BaseModel { + public declare type: Discriminator.ONE; + + @Enum("json") + public kind!: "json"; + } + + @DiscriminatorValue(Discriminator.TWO) + class SecondImpl extends BaseModel { + public declare type: Discriminator.TWO; + + @Property() + public prop!: string; + } + + class ParentModel { + @OneOf(FirstImpl, SecondImpl) + public test?: FirstImpl | SecondImpl; + } + + @Controller("/test") + @Name("Test") + class TestController { + @Get() + @Returns(200, Array).Of(ParentModel) + public get(): Promise { + return null as any; + } + } + + expect(getSpec(TestController)).toMatchSnapshot(); + }); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 40630076ddd..e0e3b943b18 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "resolveJsonModule": true, "esModuleInterop": true, "allowJs": true, - "moduleResolution": "node" + "moduleResolution": "node", + "skipLibCheck": true }, "references": [ { @@ -172,5 +173,5 @@ "path": "./packages/security/oidc-provider-plugin-wildcard-redirect-uri" } ], - "exclude": ["node_modules", "docs", "docs-references"] + "exclude": ["**/node_modules/**", "docs", "docs-references"] }