diff --git a/.changeset/wicked-rings-march.md b/.changeset/wicked-rings-march.md new file mode 100644 index 0000000000..91db8ede9d --- /dev/null +++ b/.changeset/wicked-rings-march.md @@ -0,0 +1,5 @@ +--- +"@hey-api/shared": patch +--- + +Support non-string discriminator property types (boolean, integer, number) diff --git a/packages/openapi-ts-tests/main/test/3.0.x.test.ts b/packages/openapi-ts-tests/main/test/3.0.x.test.ts index b8a28a5f91..77ed4def16 100644 --- a/packages/openapi-ts-tests/main/test/3.0.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.0.x.test.ts @@ -217,6 +217,13 @@ describe(`OpenAPI ${version}`, () => { }), description: 'handles nested allOf with discriminators', }, + { + config: createConfig({ + input: 'discriminator-non-string.yaml', + output: 'discriminator-non-string', + }), + description: 'handles non-string discriminator property types', + }, { config: createConfig({ input: 'enum-escape.json', diff --git a/packages/openapi-ts-tests/main/test/3.1.x.test.ts b/packages/openapi-ts-tests/main/test/3.1.x.test.ts index e45d157957..1bee7b8cb6 100644 --- a/packages/openapi-ts-tests/main/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.1.x.test.ts @@ -236,6 +236,13 @@ describe(`OpenAPI ${version}`, () => { }), description: 'handles nested allOf with discriminators', }, + { + config: createConfig({ + input: 'discriminator-non-string.yaml', + output: 'discriminator-non-string', + }), + description: 'handles non-string discriminator property types', + }, { config: createConfig({ input: 'discriminator-one-of-read-write.yaml', diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-non-string/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-non-string/index.ts new file mode 100644 index 0000000000..dace937e59 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-non-string/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { AutoConfig, BooleanAnyOf, BooleanOneOf, ClientOptions, CustomConfig, IntegerAllOfBase, IntegerAllOfChildA, IntegerAllOfChildB, IntegerOneOf, NumberOneOf, TypeOne, TypeTwo, VersionAlpha, VersionBeta } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-non-string/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-non-string/types.gen.ts new file mode 100644 index 0000000000..c5749a9d36 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-non-string/types.gen.ts @@ -0,0 +1,73 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type BooleanOneOf = ({ + use_custom: false; +} & AutoConfig) | ({ + use_custom: true; +} & CustomConfig); + +export type AutoConfig = { + use_custom: boolean; + auto_setting: string; +}; + +export type CustomConfig = { + use_custom: boolean; + custom_value: number; +}; + +export type BooleanAnyOf = ({ + use_custom?: false; +} & AutoConfig) | ({ + use_custom?: true; +} & CustomConfig); + +export type IntegerOneOf = ({ + type_id: 1; +} & TypeOne) | ({ + type_id: 2; +} & TypeTwo); + +export type TypeOne = { + type_id: number; + one_data: string; +}; + +export type TypeTwo = { + type_id: number; + two_data: string; +}; + +export type NumberOneOf = ({ + version: 1; +} & VersionAlpha) | ({ + version: 2.5; +} & VersionBeta); + +export type VersionAlpha = { + version: number; + alpha_field: string; +}; + +export type VersionBeta = { + version: number; + beta_field: string; +}; + +export type IntegerAllOfBase = { + kind: number; +}; + +export type IntegerAllOfChildA = Omit & { + child_a_field: string; + kind: 1; +}; + +export type IntegerAllOfChildB = Omit & { + child_b_field: string; + kind: 2; +}; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-non-string/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-non-string/index.ts new file mode 100644 index 0000000000..ea395f1ee4 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-non-string/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { AutoConfig, BooleanAnyOf, BooleanOneOf, ClientOptions, CustomConfig, IntegerAllOfBase, IntegerAllOfChildA, IntegerAllOfChildB, IntegerOneOf, NullableIntegerOneOf, NullableVariantX, NullableVariantY, NumberOneOf, TypeOne, TypeTwo, VersionAlpha, VersionBeta } from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-non-string/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-non-string/types.gen.ts new file mode 100644 index 0000000000..0ca4093758 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-non-string/types.gen.ts @@ -0,0 +1,89 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; + +export type BooleanOneOf = ({ + use_custom: false; +} & AutoConfig) | ({ + use_custom: true; +} & CustomConfig); + +export type AutoConfig = { + use_custom: false; + auto_setting: string; +}; + +export type CustomConfig = { + use_custom: true; + custom_value: number; +}; + +export type BooleanAnyOf = ({ + use_custom?: false; +} & AutoConfig) | ({ + use_custom?: true; +} & CustomConfig); + +export type IntegerOneOf = ({ + type_id: 1; +} & TypeOne) | ({ + type_id: 2; +} & TypeTwo); + +export type TypeOne = { + type_id: 1; + one_data: string; +}; + +export type TypeTwo = { + type_id: 2; + two_data: string; +}; + +export type NumberOneOf = ({ + version: 1; +} & VersionAlpha) | ({ + version: 2.5; +} & VersionBeta); + +export type VersionAlpha = { + version: 1; + alpha_field: string; +}; + +export type VersionBeta = { + version: 2.5; + beta_field: string; +}; + +export type IntegerAllOfBase = { + kind: number; +}; + +export type IntegerAllOfChildA = Omit & { + child_a_field: string; + kind: 1; +}; + +export type IntegerAllOfChildB = Omit & { + child_b_field: string; + kind: 2; +}; + +export type NullableIntegerOneOf = ({ + tag: 10; +} & NullableVariantX) | ({ + tag: 20; +} & NullableVariantY); + +export type NullableVariantX = { + tag: 10 | null; + x_data: string; +}; + +export type NullableVariantY = { + tag: 20 | null; + y_data: string; +}; diff --git a/packages/shared/src/openApi/3.0.x/parser/schema.ts b/packages/shared/src/openApi/3.0.x/parser/schema.ts index f805b8b1f1..2800f7e147 100644 --- a/packages/shared/src/openApi/3.0.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.0.x/parser/schema.ts @@ -6,7 +6,11 @@ import type { SchemaType, SchemaWithRequired, } from '../../../openApi/shared/types/schema'; -import { discriminatorValues } from '../../../openApi/shared/utils/discriminator'; +import { + convertDiscriminatorValue, + type DiscriminatorPropertyType, + discriminatorValues, +} from '../../../openApi/shared/utils/discriminator'; import { isTopLevelComponent, refToName } from '../../../utils/ref'; import type { ReferenceObject, SchemaObject } from '../types/spec'; @@ -27,6 +31,52 @@ export const getSchemaType = ({ return; }; +/** + * Finds the type of a discriminator property by looking it up in the provided schemas. + * Searches through properties and allOf chains to find the property definition. + */ +const findDiscriminatorPropertyType = ({ + context, + propertyName, + schemas, +}: { + context: Context; + propertyName: string; + schemas: ReadonlyArray; +}): DiscriminatorPropertyType => { + for (const schema of schemas) { + const resolved = '$ref' in schema ? context.resolveRef(schema.$ref) : schema; + + // Check direct properties + const property = resolved.properties?.[propertyName]; + if (property) { + const resolvedProperty = + '$ref' in property ? context.resolveRef(property.$ref) : property; + if ( + resolvedProperty.type === 'boolean' || + resolvedProperty.type === 'integer' || + resolvedProperty.type === 'number' + ) { + return resolvedProperty.type; + } + } + + // Check allOf chains + if (resolved.allOf) { + const foundType = findDiscriminatorPropertyType({ + context, + propertyName, + schemas: resolved.allOf, + }); + if (foundType !== 'string') { + return foundType; + } + } + } + + return 'string'; +}; + /** * Recursively finds discriminators in a schema, including nested allOf compositions. * This is needed when a schema extends another schema via allOf, and that parent @@ -482,10 +532,16 @@ const parseAllOf = ({ // Use allValues if we found children, otherwise use the original values const finalValues = allValues.length > 0 ? allValues : values; - const valueSchemas: ReadonlyArray = finalValues.map((value) => ({ - const: value, - type: 'string', - })); + // Detect the actual type of the discriminator property + const propertyType = findDiscriminatorPropertyType({ + context, + propertyName: discriminator.propertyName, + schemas: compositionSchemas, + }); + + const valueSchemas: ReadonlyArray = finalValues.map((value) => + convertDiscriminatorValue(value, propertyType), + ); const discriminatorProperty: IR.SchemaObject = valueSchemas.length > 1 @@ -674,6 +730,14 @@ const parseAnyOf = ({ const compositionSchemas = schema.anyOf; + const discriminatorPropertyType = schema.discriminator + ? findDiscriminatorPropertyType({ + context, + propertyName: schema.discriminator.propertyName, + schemas: compositionSchemas, + }) + : undefined; + for (const compositionSchema of compositionSchemas) { let irCompositionSchema = schemaToIrSchema({ context, @@ -684,10 +748,10 @@ const parseAnyOf = ({ // `$ref` should be defined with discriminators if (schema.discriminator && irCompositionSchema.$ref != null) { const values = discriminatorValues(irCompositionSchema.$ref, schema.discriminator.mapping); - const valueSchemas: ReadonlyArray = values.map((value) => ({ - const: value, - type: 'string', - })); + + const valueSchemas: ReadonlyArray = values.map((value) => + convertDiscriminatorValue(value, discriminatorPropertyType!), + ); const irDiscriminatorSchema: IR.SchemaObject = { properties: { [schema.discriminator.propertyName]: @@ -834,6 +898,14 @@ const parseOneOf = ({ const compositionSchemas = schema.oneOf; + const discriminatorPropertyType = schema.discriminator + ? findDiscriminatorPropertyType({ + context, + propertyName: schema.discriminator.propertyName, + schemas: compositionSchemas, + }) + : undefined; + for (const compositionSchema of compositionSchemas) { let irCompositionSchema = schemaToIrSchema({ context, @@ -844,10 +916,10 @@ const parseOneOf = ({ // `$ref` should be defined with discriminators if (schema.discriminator && irCompositionSchema.$ref != null) { const values = discriminatorValues(irCompositionSchema.$ref, schema.discriminator.mapping); - const valueSchemas: ReadonlyArray = values.map((value) => ({ - const: value, - type: 'string', - })); + + const valueSchemas: ReadonlyArray = values.map((value) => + convertDiscriminatorValue(value, discriminatorPropertyType!), + ); const irDiscriminatorSchema: IR.SchemaObject = { properties: { [schema.discriminator.propertyName]: diff --git a/packages/shared/src/openApi/3.1.x/parser/schema.ts b/packages/shared/src/openApi/3.1.x/parser/schema.ts index 0cad93884b..f104b86c17 100644 --- a/packages/shared/src/openApi/3.1.x/parser/schema.ts +++ b/packages/shared/src/openApi/3.1.x/parser/schema.ts @@ -6,7 +6,11 @@ import type { SchemaType, SchemaWithRequired, } from '../../../openApi/shared/types/schema'; -import { discriminatorValues } from '../../../openApi/shared/utils/discriminator'; +import { + convertDiscriminatorValue, + type DiscriminatorPropertyType, + discriminatorValues, +} from '../../../openApi/shared/utils/discriminator'; import { isTopLevelComponent, refToName } from '../../../utils/ref'; import type { SchemaObject } from '../types/spec'; @@ -31,6 +35,60 @@ export const getSchemaTypes = ({ return []; }; +/** + * Finds the type of a discriminator property by looking it up in the provided schemas. + * Searches through properties and allOf chains to find the property definition. + */ +const findDiscriminatorPropertyType = ({ + context, + propertyName, + schemas, +}: { + context: Context; + propertyName: string; + schemas: ReadonlyArray; +}): DiscriminatorPropertyType => { + for (const schema of schemas) { + const resolved = schema.$ref ? context.resolveRef(schema.$ref) : schema; + + // Check direct properties + const property = resolved.properties?.[propertyName]; + if (property === true) { + continue; + } + if (property) { + const resolvedProperty = property.$ref + ? context.resolveRef(property.$ref) + : property; + // Handle both single type and array of types (3.1.x supports type arrays) + const propertyTypes = Array.isArray(resolvedProperty.type) + ? resolvedProperty.type + : resolvedProperty.type + ? [resolvedProperty.type] + : []; + for (const propType of propertyTypes) { + if (propType === 'boolean' || propType === 'integer' || propType === 'number') { + return propType; + } + } + } + + // Check allOf chains + if (resolved.allOf) { + const foundType = findDiscriminatorPropertyType({ + context, + propertyName, + schemas: resolved.allOf, + }); + if (foundType !== 'string') { + return foundType; + } + } + } + + return 'string'; +}; + /** * Recursively finds discriminators in a schema, including nested allOf compositions. * This is needed when a schema extends another schema via allOf, and that parent @@ -564,10 +622,16 @@ const parseAllOf = ({ // Use allValues if we found children, otherwise use the original values const finalValues = allValues.length > 0 ? allValues : values; - const valueSchemas: ReadonlyArray = finalValues.map((value) => ({ - const: value, - type: 'string', - })); + // Detect the actual type of the discriminator property + const propertyType = findDiscriminatorPropertyType({ + context, + propertyName: discriminator.propertyName, + schemas: compositionSchemas, + }); + + const valueSchemas: ReadonlyArray = finalValues.map((value) => + convertDiscriminatorValue(value, propertyType), + ); const discriminatorProperty: IR.SchemaObject = valueSchemas.length > 1 @@ -743,6 +807,14 @@ const parseAnyOf = ({ const compositionSchemas = schema.anyOf; + const discriminatorPropertyType = schema.discriminator + ? findDiscriminatorPropertyType({ + context, + propertyName: schema.discriminator.propertyName, + schemas: compositionSchemas, + }) + : undefined; + for (const compositionSchema of compositionSchemas) { let irCompositionSchema = schemaToIrSchema({ context, @@ -753,10 +825,10 @@ const parseAnyOf = ({ // `$ref` should be defined with discriminators if (schema.discriminator && irCompositionSchema.$ref != null) { const values = discriminatorValues(irCompositionSchema.$ref, schema.discriminator.mapping); - const valueSchemas: ReadonlyArray = values.map((value) => ({ - const: value, - type: 'string', - })); + + const valueSchemas: ReadonlyArray = values.map((value) => + convertDiscriminatorValue(value, discriminatorPropertyType!), + ); const irDiscriminatorSchema: IR.SchemaObject = { properties: { [schema.discriminator.propertyName]: @@ -894,6 +966,14 @@ const parseOneOf = ({ const compositionSchemas = schema.oneOf; + const discriminatorPropertyType = schema.discriminator + ? findDiscriminatorPropertyType({ + context, + propertyName: schema.discriminator.propertyName, + schemas: compositionSchemas, + }) + : undefined; + for (const compositionSchema of compositionSchemas) { let irCompositionSchema = schemaToIrSchema({ context, @@ -904,10 +984,10 @@ const parseOneOf = ({ // `$ref` should be defined with discriminators if (schema.discriminator && irCompositionSchema.$ref != null) { const values = discriminatorValues(irCompositionSchema.$ref, schema.discriminator.mapping); - const valueSchemas: ReadonlyArray = values.map((value) => ({ - const: value, - type: 'string', - })); + + const valueSchemas: ReadonlyArray = values.map((value) => + convertDiscriminatorValue(value, discriminatorPropertyType!), + ); const irDiscriminatorSchema: IR.SchemaObject = { properties: { [schema.discriminator.propertyName]: diff --git a/packages/shared/src/openApi/shared/utils/discriminator.ts b/packages/shared/src/openApi/shared/utils/discriminator.ts index 836af5986a..68f09219ce 100644 --- a/packages/shared/src/openApi/shared/utils/discriminator.ts +++ b/packages/shared/src/openApi/shared/utils/discriminator.ts @@ -1,5 +1,83 @@ +import type { IR } from '../../../ir/types'; import { refToName } from '../../../utils/ref'; +/** + * Supported types for discriminator properties. + */ +export type DiscriminatorPropertyType = 'boolean' | 'integer' | 'number' | 'string'; + +/** + * Converts a string discriminator mapping value to the appropriate type based on + * the actual property type in the schema. + * + * OpenAPI discriminator mappings always use string keys, but the actual discriminator + * property may be a boolean, number, or integer. This function converts the string + * key to the correct runtime value and IR type. + */ +export const convertDiscriminatorValue = ( + value: string, + propertyType: DiscriminatorPropertyType, +): { const: IR.SchemaObject['const']; type: IR.SchemaObject['type'] } => { + switch (propertyType) { + case 'boolean': { + const lowerValue = value.toLowerCase(); + if (lowerValue !== 'true' && lowerValue !== 'false') { + console.warn( + '🚨', + `non-boolean discriminator mapping value "${value}" for boolean property, falling back to string`, + ); + return { + const: value, + type: 'string', + }; + } + return { + const: lowerValue === 'true', + type: 'boolean', + }; + } + case 'integer': { + const parsed = parseInt(value, 10); + if (Number.isNaN(parsed)) { + console.warn( + '🚨', + `non-numeric discriminator mapping value "${value}" for integer property, falling back to string`, + ); + return { + const: value, + type: 'string', + }; + } + return { + const: parsed, + type: 'integer', + }; + } + case 'number': { + const parsed = parseFloat(value); + if (Number.isNaN(parsed)) { + console.warn( + '🚨', + `non-numeric discriminator mapping value "${value}" for number property, falling back to string`, + ); + return { + const: value, + type: 'string', + }; + } + return { + const: parsed, + type: 'number', + }; + } + default: + return { + const: value, + type: 'string', + }; + } +}; + export const discriminatorValues = ( $ref: string, mapping?: Record, diff --git a/specs/3.0.x/discriminator-non-string.yaml b/specs/3.0.x/discriminator-non-string.yaml new file mode 100644 index 0000000000..bf82c217f3 --- /dev/null +++ b/specs/3.0.x/discriminator-non-string.yaml @@ -0,0 +1,155 @@ +openapi: 3.0.3 +info: + title: Non-string discriminator test + version: 1 +components: + schemas: + # --- Boolean discriminator (oneOf) --- + BooleanOneOf: + oneOf: + - $ref: '#/components/schemas/AutoConfig' + - $ref: '#/components/schemas/CustomConfig' + discriminator: + propertyName: use_custom + mapping: + 'False': '#/components/schemas/AutoConfig' + 'True': '#/components/schemas/CustomConfig' + + AutoConfig: + type: object + required: + - use_custom + - auto_setting + properties: + use_custom: + type: boolean + const: false + auto_setting: + type: string + + CustomConfig: + type: object + required: + - use_custom + - custom_value + properties: + use_custom: + type: boolean + const: true + custom_value: + type: integer + + # --- Boolean discriminator (anyOf) --- + BooleanAnyOf: + anyOf: + - $ref: '#/components/schemas/AutoConfig' + - $ref: '#/components/schemas/CustomConfig' + discriminator: + propertyName: use_custom + mapping: + 'False': '#/components/schemas/AutoConfig' + 'True': '#/components/schemas/CustomConfig' + + # --- Integer discriminator (oneOf) --- + IntegerOneOf: + oneOf: + - $ref: '#/components/schemas/TypeOne' + - $ref: '#/components/schemas/TypeTwo' + discriminator: + propertyName: type_id + mapping: + '1': '#/components/schemas/TypeOne' + '2': '#/components/schemas/TypeTwo' + + TypeOne: + type: object + required: + - type_id + - one_data + properties: + type_id: + type: integer + const: 1 + one_data: + type: string + + TypeTwo: + type: object + required: + - type_id + - two_data + properties: + type_id: + type: integer + const: 2 + two_data: + type: string + + # --- Number (float) discriminator (oneOf) --- + NumberOneOf: + oneOf: + - $ref: '#/components/schemas/VersionAlpha' + - $ref: '#/components/schemas/VersionBeta' + discriminator: + propertyName: version + mapping: + '1.0': '#/components/schemas/VersionAlpha' + '2.5': '#/components/schemas/VersionBeta' + + VersionAlpha: + type: object + required: + - version + - alpha_field + properties: + version: + type: number + const: 1.0 + alpha_field: + type: string + + VersionBeta: + type: object + required: + - version + - beta_field + properties: + version: + type: number + const: 2.5 + beta_field: + type: string + + # --- Integer discriminator (allOf) --- + IntegerAllOfBase: + type: object + required: + - kind + properties: + kind: + type: integer + discriminator: + propertyName: kind + mapping: + '1': '#/components/schemas/IntegerAllOfChildA' + '2': '#/components/schemas/IntegerAllOfChildB' + + IntegerAllOfChildA: + allOf: + - $ref: '#/components/schemas/IntegerAllOfBase' + - type: object + required: + - child_a_field + properties: + child_a_field: + type: string + + IntegerAllOfChildB: + allOf: + - $ref: '#/components/schemas/IntegerAllOfBase' + - type: object + required: + - child_b_field + properties: + child_b_field: + type: string diff --git a/specs/3.1.x/discriminator-non-string.yaml b/specs/3.1.x/discriminator-non-string.yaml new file mode 100644 index 0000000000..78c6b0e5c6 --- /dev/null +++ b/specs/3.1.x/discriminator-non-string.yaml @@ -0,0 +1,194 @@ +openapi: 3.1.0 +info: + title: Non-string discriminator test + version: 1 +components: + schemas: + # --- Boolean discriminator (oneOf) --- + BooleanOneOf: + oneOf: + - $ref: '#/components/schemas/AutoConfig' + - $ref: '#/components/schemas/CustomConfig' + discriminator: + propertyName: use_custom + mapping: + 'False': '#/components/schemas/AutoConfig' + 'True': '#/components/schemas/CustomConfig' + + AutoConfig: + type: object + required: + - use_custom + - auto_setting + properties: + use_custom: + type: boolean + const: false + auto_setting: + type: string + + CustomConfig: + type: object + required: + - use_custom + - custom_value + properties: + use_custom: + type: boolean + const: true + custom_value: + type: integer + + # --- Boolean discriminator (anyOf) --- + BooleanAnyOf: + anyOf: + - $ref: '#/components/schemas/AutoConfig' + - $ref: '#/components/schemas/CustomConfig' + discriminator: + propertyName: use_custom + mapping: + 'False': '#/components/schemas/AutoConfig' + 'True': '#/components/schemas/CustomConfig' + + # --- Integer discriminator (oneOf) --- + IntegerOneOf: + oneOf: + - $ref: '#/components/schemas/TypeOne' + - $ref: '#/components/schemas/TypeTwo' + discriminator: + propertyName: type_id + mapping: + '1': '#/components/schemas/TypeOne' + '2': '#/components/schemas/TypeTwo' + + TypeOne: + type: object + required: + - type_id + - one_data + properties: + type_id: + type: integer + const: 1 + one_data: + type: string + + TypeTwo: + type: object + required: + - type_id + - two_data + properties: + type_id: + type: integer + const: 2 + two_data: + type: string + + # --- Number (float) discriminator (oneOf) --- + NumberOneOf: + oneOf: + - $ref: '#/components/schemas/VersionAlpha' + - $ref: '#/components/schemas/VersionBeta' + discriminator: + propertyName: version + mapping: + '1.0': '#/components/schemas/VersionAlpha' + '2.5': '#/components/schemas/VersionBeta' + + VersionAlpha: + type: object + required: + - version + - alpha_field + properties: + version: + type: number + const: 1.0 + alpha_field: + type: string + + VersionBeta: + type: object + required: + - version + - beta_field + properties: + version: + type: number + const: 2.5 + beta_field: + type: string + + # --- Integer discriminator (allOf) --- + IntegerAllOfBase: + type: object + required: + - kind + properties: + kind: + type: integer + discriminator: + propertyName: kind + mapping: + '1': '#/components/schemas/IntegerAllOfChildA' + '2': '#/components/schemas/IntegerAllOfChildB' + + IntegerAllOfChildA: + allOf: + - $ref: '#/components/schemas/IntegerAllOfBase' + - type: object + required: + - child_a_field + properties: + child_a_field: + type: string + + IntegerAllOfChildB: + allOf: + - $ref: '#/components/schemas/IntegerAllOfBase' + - type: object + required: + - child_b_field + properties: + child_b_field: + type: string + + # --- 3.1.x-specific: discriminator property with type array (e.g. nullable integer) --- + NullableIntegerOneOf: + oneOf: + - $ref: '#/components/schemas/NullableVariantX' + - $ref: '#/components/schemas/NullableVariantY' + discriminator: + propertyName: tag + mapping: + '10': '#/components/schemas/NullableVariantX' + '20': '#/components/schemas/NullableVariantY' + + NullableVariantX: + type: object + required: + - tag + - x_data + properties: + tag: + type: + - integer + - 'null' + const: 10 + x_data: + type: string + + NullableVariantY: + type: object + required: + - tag + - y_data + properties: + tag: + type: + - integer + - 'null' + const: 20 + y_data: + type: string