diff --git a/src/interpreter/InterpretAllOf.ts b/src/interpreter/InterpretAllOf.ts index 7f62cd34ea..fe511ee97f 100644 --- a/src/interpreter/InterpretAllOf.ts +++ b/src/interpreter/InterpretAllOf.ts @@ -1,5 +1,5 @@ import { Logger } from '../utils'; -import { CommonModel, AsyncapiV2Schema } from '../models'; +import { CommonModel } from '../models'; import { Interpreter, InterpreterOptions, @@ -33,13 +33,13 @@ export default function interpretAllOf( } for (const allOfSchema of schema.allOf) { - if (allOfSchema instanceof AsyncapiV2Schema && allOfSchema.discriminator) { + const discriminator = interpreter.discriminatorProperty(allOfSchema); + if (discriminator !== undefined) { interpreterOptions = { ...interpreterOptions, - discriminator: allOfSchema.discriminator + discriminator }; - - model.discriminator = allOfSchema.discriminator; + model.discriminator = discriminator; } } diff --git a/src/interpreter/InterpretConst.ts b/src/interpreter/InterpretConst.ts index c1a95bb84f..905296704c 100644 --- a/src/interpreter/InterpretConst.ts +++ b/src/interpreter/InterpretConst.ts @@ -1,4 +1,10 @@ -import { Draft4Schema, CommonModel, AsyncapiV2Schema } from '../models'; +import { + Draft4Schema, + CommonModel, + AsyncapiV2Schema, + SwaggerV2Schema, + OpenapiV3Schema +} from '../models'; import { Interpreter, InterpreterOptions, @@ -26,7 +32,12 @@ export default function interpretConst( return; } - if (schema instanceof AsyncapiV2Schema && interpreterOptions.discriminator) { + if ( + (schema instanceof AsyncapiV2Schema || + schema instanceof SwaggerV2Schema || + schema instanceof OpenapiV3Schema) && + interpreterOptions.discriminator + ) { model.enum = [schema.const]; } diff --git a/src/interpreter/InterpretOneOf.ts b/src/interpreter/InterpretOneOf.ts index 81dc2f8161..14cf4650ac 100644 --- a/src/interpreter/InterpretOneOf.ts +++ b/src/interpreter/InterpretOneOf.ts @@ -1,4 +1,4 @@ -import { AsyncapiV2Schema, CommonModel } from '../models'; +import { CommonModel } from '../models'; import { Interpreter, InterpreterOptions, @@ -30,14 +30,12 @@ export default function interpretOneOf( return; } - if (schema instanceof AsyncapiV2Schema && schema.discriminator) { - interpreterOptions = { - ...interpreterOptions, - discriminator: schema.discriminator - }; - - model.discriminator = schema.discriminator; - } + const discriminator = interpreter.discriminatorProperty(schema); + interpreterOptions = { + ...interpreterOptions, + discriminator + }; + model.discriminator = discriminator; for (const oneOfSchema of schema.oneOf) { const oneOfModel = interpreter.interpret(oneOfSchema, interpreterOptions); diff --git a/src/interpreter/InterpretOneOfWithAllOf.ts b/src/interpreter/InterpretOneOfWithAllOf.ts index c5e4131c30..0ad5ce835d 100644 --- a/src/interpreter/InterpretOneOfWithAllOf.ts +++ b/src/interpreter/InterpretOneOfWithAllOf.ts @@ -1,4 +1,4 @@ -import { CommonModel, AsyncapiV2Schema } from '../models'; +import { CommonModel } from '../models'; import { Interpreter, InterpreterOptions, @@ -32,13 +32,13 @@ export default function interpretOneOfWithAllOf( } for (const allOfSchema of schema.allOf) { - if (allOfSchema instanceof AsyncapiV2Schema && allOfSchema.discriminator) { + const discriminator = interpreter.discriminatorProperty(allOfSchema); + if (discriminator !== undefined) { interpreterOptions = { ...interpreterOptions, - discriminator: allOfSchema.discriminator + discriminator }; - - model.discriminator = allOfSchema.discriminator; + model.discriminator = discriminator; } } diff --git a/src/interpreter/InterpretOneOfWithProperties.ts b/src/interpreter/InterpretOneOfWithProperties.ts index 6016031ced..dae5e09fb5 100644 --- a/src/interpreter/InterpretOneOfWithProperties.ts +++ b/src/interpreter/InterpretOneOfWithProperties.ts @@ -1,4 +1,4 @@ -import { CommonModel, AsyncapiV2Schema } from '../models'; +import { CommonModel } from '../models'; import { Interpreter, InterpreterOptions, @@ -30,14 +30,12 @@ export default function interpretOneOfWithProperties( return; } - if (schema instanceof AsyncapiV2Schema && schema.discriminator) { - interpreterOptions = { - ...interpreterOptions, - discriminator: schema.discriminator - }; - - model.discriminator = schema.discriminator; - } + const discriminator = interpreter.discriminatorProperty(schema); + interpreterOptions = { + ...interpreterOptions, + discriminator + }; + model.discriminator = discriminator; for (const oneOfSchema of schema.oneOf) { const oneOfModel = interpreter.interpret(oneOfSchema, interpreterOptions); diff --git a/src/interpreter/Interpreter.ts b/src/interpreter/Interpreter.ts index c8d75501df..f3e3075056 100644 --- a/src/interpreter/Interpreter.ts +++ b/src/interpreter/Interpreter.ts @@ -6,7 +6,8 @@ import { AsyncapiV2Schema, Draft7Schema, MergingOptions, - defaultMergingOptions + defaultMergingOptions, + OpenapiV3Schema } from '../models'; import { interpretName } from './Utils'; import interpretProperties from './InterpretProperties'; @@ -46,7 +47,7 @@ export type InterpreterOptions = { */ ignoreAdditionalItems?: boolean; /** - * When interpreting a schema with discriminator set, this property will be set bet by the individual interpreters to make sure the discriminator becomes an enum. + * When interpreting a schema with discriminator set, this property will be set best by the individual interpreters to make sure the discriminator becomes an enum. */ discriminator?: string; }; @@ -55,6 +56,7 @@ export type InterpreterSchemas = | Draft4Schema | Draft7Schema | SwaggerV2Schema + | OpenapiV3Schema | AsyncapiV2Schema; export type InterpreterSchemaType = InterpreterSchemas | boolean; @@ -184,4 +186,26 @@ export class Interpreter { ); } } + + /** + * Get the discriminator property name for the schema, if the schema has one + * + * @param schema + * @returns discriminator name property + */ + discriminatorProperty(schema: InterpreterSchemaType): string | undefined { + if ( + (schema instanceof AsyncapiV2Schema || + schema instanceof SwaggerV2Schema) && + schema.discriminator + ) { + return schema.discriminator; + } else if ( + schema instanceof OpenapiV3Schema && + schema.discriminator && + schema.discriminator.propertyName + ) { + return schema.discriminator.propertyName; + } + } } diff --git a/src/processors/JsonSchemaInputProcessor.ts b/src/processors/JsonSchemaInputProcessor.ts index d63bf5f024..b64aae1a66 100644 --- a/src/processors/JsonSchemaInputProcessor.ts +++ b/src/processors/JsonSchemaInputProcessor.ts @@ -476,6 +476,7 @@ export class JsonSchemaInputProcessor extends AbstractInputProcessor { | Draft6Schema | Draft7Schema | SwaggerV2Schema + | OpenapiV3Schema | AsyncapiV2Schema | boolean, options?: ProcessorOptions diff --git a/src/processors/OpenAPIInputProcessor.ts b/src/processors/OpenAPIInputProcessor.ts index c792588dde..bd05e36fac 100644 --- a/src/processors/OpenAPIInputProcessor.ts +++ b/src/processors/OpenAPIInputProcessor.ts @@ -222,14 +222,14 @@ export class OpenAPIInputProcessor extends AbstractInputProcessor { schema: OpenAPIV3.SchemaObject, name: string ): OpenapiV3Schema { - let internalSchema = OpenapiV3Schema.toSchema(schema as any); - internalSchema = JsonSchemaInputProcessor.reflectSchemaNames( - internalSchema, + const namedSchema = JsonSchemaInputProcessor.reflectSchemaNames( + schema, {}, name, true ); - return internalSchema; + + return OpenapiV3Schema.toSchema(namedSchema); } /** diff --git a/test/generators/java/presets/JacksonPreset.spec.ts b/test/generators/java/presets/JacksonPreset.spec.ts index e2ffed8a3e..efe0646fae 100644 --- a/test/generators/java/presets/JacksonPreset.spec.ts +++ b/test/generators/java/presets/JacksonPreset.spec.ts @@ -45,7 +45,7 @@ describe('JAVA_JACKSON_PRESET', () => { }); describe('union', () => { - test('handle oneOf with discriminator with Jackson', async () => { + test('handle oneOf with AsyncAPI discriminator with Jackson', async () => { const asyncapiDoc = { asyncapi: '2.6.0', info: { @@ -91,6 +91,112 @@ describe('JAVA_JACKSON_PRESET', () => { expect(models.map((model) => model.result)).toMatchSnapshot(); }); + test('handle oneOf with Swagger v2 discriminator with Jackson', async () => { + const openapiDoc = { + swagger: '2.0', + info: { + title: 'Vehicle', + version: '1.0.0' + }, + paths: { + '/vehicles': { + get: { + responses: { + 200: { + description: 'successful operation', + schema: { + type: 'object', + title: 'Vehicle', + discriminator: 'vehicleType', + oneOf: [ + { $ref: '#/components/schemas/Car' }, + { $ref: '#/components/schemas/Truck' } + ] + } + } + } + } + } + }, + components: { + schemas: { + Car: { + title: 'Car', + type: 'object', + properties: { + vehicleType: { type: 'string' } + } + }, + Truck: { + title: 'Truck', + type: 'object', + properties: { + vehicleType: { type: 'string' } + } + } + } + } + }; + + const models = await generator.generate(openapiDoc); + expect(models.map((model) => model.result)).toMatchSnapshot(); + }); + + test('handle oneOf with OpenAPI v3 discriminator with Jackson', async () => { + const openapiDoc = { + openapi: '3.0.3', + info: { + title: 'Vehicle', + version: '1.0.0' + }, + paths: { + '/vehicles': { + get: { + responses: { + 200: { + description: 'successful operation', + content: { + 'application/json': { + schema: { + type: 'object', + title: 'Vehicle', + discriminator: { propertyName: 'vehicleType' }, + oneOf: [ + { $ref: '#/components/schemas/Car' }, + { $ref: '#/components/schemas/Truck' } + ] + } + } + } + } + } + } + } + }, + components: { + schemas: { + Car: { + title: 'Car', + type: 'object', + properties: { + vehicleType: { type: 'string' } + } + }, + Truck: { + title: 'Truck', + type: 'object', + properties: { + vehicleType: { type: 'string' } + } + } + } + } + }; + + const models = await generator.generate(openapiDoc); + expect(models.map((model) => model.result)).toMatchSnapshot(); + }); + test('handle oneOf without discriminator with Jackson deduction', async () => { const jsonSchemaDraft7 = { $schema: 'http://json-schema.org/draft-07/schema#', diff --git a/test/generators/java/presets/__snapshots__/JacksonPreset.spec.ts.snap b/test/generators/java/presets/__snapshots__/JacksonPreset.spec.ts.snap index 911308d71b..87907b2af8 100644 --- a/test/generators/java/presets/__snapshots__/JacksonPreset.spec.ts.snap +++ b/test/generators/java/presets/__snapshots__/JacksonPreset.spec.ts.snap @@ -51,7 +51,7 @@ exports[`JAVA_JACKSON_PRESET should render Jackson annotations for enum 1`] = ` }" `; -exports[`JAVA_JACKSON_PRESET union handle oneOf with discriminator with Jackson 1`] = ` +exports[`JAVA_JACKSON_PRESET union handle oneOf with AsyncAPI discriminator with Jackson 1`] = ` Array [ "@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property=\\"vehicleType\\") @JsonSubTypes({ @@ -99,6 +99,82 @@ public interface Vehicle { ] `; +exports[`JAVA_JACKSON_PRESET union handle oneOf with OpenAPI v3 discriminator with Jackson 1`] = ` +Array [ + "@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property=\\"vehicleType\\") +@JsonSubTypes({ + @JsonSubTypes.Type(value = Car.class, name = \\"Car\\"), + @JsonSubTypes.Type(value = Truck.class, name = \\"Truck\\") +}) +/** + * Vehicle represents a union of types: Car, Truck + */ +public interface Vehicle { + String getVehicleType(); +}", + "public class Car implements Vehicle { + @JsonProperty(\\"vehicleType\\") + private String vehicleType; + private Map additionalProperties; + + public String getVehicleType() { return this.vehicleType; } + public void setVehicleType(String vehicleType) { this.vehicleType = vehicleType; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } +}", + "public class Truck implements Vehicle { + @JsonProperty(\\"vehicleType\\") + private String vehicleType; + private Map additionalProperties; + + public String getVehicleType() { return this.vehicleType; } + public void setVehicleType(String vehicleType) { this.vehicleType = vehicleType; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } +}", +] +`; + +exports[`JAVA_JACKSON_PRESET union handle oneOf with Swagger v2 discriminator with Jackson 1`] = ` +Array [ + "@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property=\\"vehicleType\\") +@JsonSubTypes({ + @JsonSubTypes.Type(value = Car.class, name = \\"Car\\"), + @JsonSubTypes.Type(value = Truck.class, name = \\"Truck\\") +}) +/** + * Vehicle represents a union of types: Car, Truck + */ +public interface Vehicle { + String getVehicleType(); +}", + "public class Car implements Vehicle { + @JsonProperty(\\"vehicleType\\") + private String vehicleType; + private Map additionalProperties; + + public String getVehicleType() { return this.vehicleType; } + public void setVehicleType(String vehicleType) { this.vehicleType = vehicleType; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } +}", + "public class Truck implements Vehicle { + @JsonProperty(\\"vehicleType\\") + private String vehicleType; + private Map additionalProperties; + + public String getVehicleType() { return this.vehicleType; } + public void setVehicleType(String vehicleType) { this.vehicleType = vehicleType; } + + public Map getAdditionalProperties() { return this.additionalProperties; } + public void setAdditionalProperties(Map additionalProperties) { this.additionalProperties = additionalProperties; } +}", +] +`; + exports[`JAVA_JACKSON_PRESET union handle oneOf without discriminator with Jackson deduction 1`] = ` Array [ "@JsonTypeInfo(use=JsonTypeInfo.Id.DEDUCTION) diff --git a/test/interpreter/unit/InterpretAllOf.spec.ts b/test/interpreter/unit/InterpretAllOf.spec.ts index 64a3a57da6..5f385b2448 100644 --- a/test/interpreter/unit/InterpretAllOf.spec.ts +++ b/test/interpreter/unit/InterpretAllOf.spec.ts @@ -122,10 +122,14 @@ describe('Interpretation of allOf', () => { const model = new CommonModel(); const interpreter = new Interpreter(); (interpreter.interpret as jest.Mock).mockReturnValue(new CommonModel()); + (interpreter.discriminatorProperty as jest.Mock).mockReturnValue( + item1.discriminator + ); interpretAllOf(schema, model, interpreter, {}); expect(model.discriminator).toBe(item1.discriminator); + expect(interpreter.discriminatorProperty).toHaveBeenCalledWith(item1); expect(interpreter.interpretAndCombineSchema).toHaveBeenCalledWith( item1, model, diff --git a/test/interpreter/unit/InterpretOneOf.spec.ts b/test/interpreter/unit/InterpretOneOf.spec.ts index 05005340a8..a9740a70b4 100644 --- a/test/interpreter/unit/InterpretOneOf.spec.ts +++ b/test/interpreter/unit/InterpretOneOf.spec.ts @@ -62,10 +62,14 @@ describe('Interpretation of oneOf', () => { }); const model = new CommonModel(); const interpreter = new Interpreter(); + (interpreter.discriminatorProperty as jest.Mock).mockReturnValue( + schema.discriminator + ); InterpretOneOf(schema, model, interpreter, {}); expect(model.discriminator).toBe(schema.discriminator); + expect(interpreter.discriminatorProperty).toHaveBeenCalledWith(schema); expect(interpreter.interpret).toHaveBeenCalledWith(item1, { discriminator: schema.discriminator }); diff --git a/test/interpreter/unit/Interpreter.spec.ts b/test/interpreter/unit/Interpreter.spec.ts index e160a93a30..d9a0c2175b 100644 --- a/test/interpreter/unit/Interpreter.spec.ts +++ b/test/interpreter/unit/Interpreter.spec.ts @@ -14,7 +14,13 @@ import interpretAdditionalItems from '../../../src/interpreter/InterpretAddition import interpretNot from '../../../src/interpreter/InterpretNot'; import interpretDependencies from '../../../src/interpreter/InterpretDependencies'; import InterpretThenElse from '../../../src/interpreter/InterpretThenElse'; -import { CommonModel, defaultMergingOptions } from '../../../src/models'; +import { + AsyncapiV2Schema, + CommonModel, + OpenapiV3Schema, + SwaggerV2Schema, + defaultMergingOptions +} from '../../../src/models'; import { Draft7Schema } from '../../../src/models/Draft7Schema'; jest.mock('../../../src/interpreter/Utils'); @@ -271,4 +277,38 @@ describe('Interpreter', () => { type: 'string' }); }); + describe('discriminatorProperty', () => { + test('should interpret AsyncapiV2Schema discriminator property', () => { + const schema = AsyncapiV2Schema.toSchema({ + discriminator: 'AsyncapiV2SchemaDiscriminatorPropertyName', + type: 'object', + $id: 'test' + }); + const interpreter = new Interpreter(); + const discriminator = interpreter.discriminatorProperty(schema); + expect(discriminator).toBe('AsyncapiV2SchemaDiscriminatorPropertyName'); + }); + test('should interpret SwaggerV2Schema discriminator property', () => { + const schema = SwaggerV2Schema.toSchema({ + discriminator: 'SwaggerV2SchemaDiscriminatorPropertyName', + type: 'object', + $id: 'test' + }); + const interpreter = new Interpreter(); + const discriminator = interpreter.discriminatorProperty(schema); + expect(discriminator).toBe('SwaggerV2SchemaDiscriminatorPropertyName'); + }); + test('should interpret OpenapiV3Schema discriminator property', () => { + const schema = OpenapiV3Schema.toSchema({ + discriminator: { + propertyName: 'OpenapiV3SchemaDiscriminatorPropertyName' + }, + type: 'object', + $id: 'test' + }); + const interpreter = new Interpreter(); + const discriminator = interpreter.discriminatorProperty(schema); + expect(discriminator).toBe('OpenapiV3SchemaDiscriminatorPropertyName'); + }); + }); });