I'm using openapi-generator
6.2.1
for generating both server-side Java Spring interfaces and TypeScript client classes from openapi
3.0.2
YAML files.
I'm trying to use the oneOf
functionality to specify that a particular property can have one of two exact types: PublicMetadataSchemaV1
or ClosedMetadataSchemaV1
.
Here's the definition:
ReadRaidMetadataResponseV1:
# Using oneOf/discriminator is probably pushing too close to the edge of
# what openapi-gen can do yet, for example that's why metadataSchema is a
# string instead of an enum:
# https://github.com/OpenAPITools/openapi-generator/pull/13846
type: object
description: Any type of metadata
oneOf:
# - $ref: 'metadata-schema-v1.yaml#/components/schemas/MetadataSchemaV1'
- $ref: '#/components/schemas/PublicMetadataSchemaV1'
- $ref: '#/components/schemas/ClosedMetadataSchemaV1'
discriminator:
propertyName: metadataSchema
mapping:
raido-metadata-schema-v1: '#/components/schemas/PublicMetadataSchemaV1'
closed-metadata-schema-v1: '#/components/schemas/ClosedMetadataSchemaV1'
PublicMetadataSchemaV1:
description: >
This object only exists because openapi-gen discriminator needs to be
a string, currently.
See https://github.com/OpenAPITools/openapi-generator/pull/13846.
Eventually, want the mapping to just use MetadataSchemaV1.
type: object
required: [ metadataSchema, id, titles, dates, access]
# this is how we make PublicMetadataSchemaV1 inherit all the fields oneOf:
# MetadataSchemaV1.
allOf:
- $ref: 'metadata-schema-v1.yaml#/components/schemas/MetadataSchemaV1'
properties:
# This is where we "override" the type of metadataSchema to be a string
# instead of the enum that we would prefer it to be.
# Rather than "string" this should be a "constant" with value `raido-metadata-schema-v1`
# metadataSchema: {$ref: 'shared.yaml#/components/schemas/RaidoMetaschema' }
metadataSchema: { type: string }
ClosedMetadataSchemaV1:
type: object
required: [ metadataSchema, id, titles, dates, access]
properties:
# Rather than "string" this should be a "constant" with value `closed-metadata-schema-v1`
# metadataSchema: {$ref: 'shared.yaml#/components/schemas/RaidoMetaschema' }
metadataSchema: { type: string }
id: {$ref: 'shared.yaml#/components/schemas/IdBlock'}
access: {$ref: 'shared.yaml#/components/schemas/AccessBlock'}
The problem here is that the JavaSpring
generator creates code that looks like:
@JsonIgnoreProperties(
value = "metadataSchema", // ignore manually set metadataSchema, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the metadataSchema to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "metadataSchema", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ClosedMetadataSchemaV1.class, name = "ClosedMetadataSchemaV1"),
@JsonSubTypes.Type(value = PublicMetadataSchemaV1.class, name = "PublicMetadataSchemaV1"),
@JsonSubTypes.Type(value = ClosedMetadataSchemaV1.class, name = "closed-metadata-schema-v1"),
@JsonSubTypes.Type(value = PublicMetadataSchemaV1.class, name = "raido-metadata-schema-v1")
})
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-11-21T15:03:48.931461200+10:00[Australia/Brisbane]")
public interface ReadRaidMetadataResponseV1 {
public String getMetadataSchema();
}
And the API service always serves the "class name" mapping (i.e. PublicMetadataSchemaV1
) as the value instead of the defined "mapping" value of raido-metadata-schema-v1
.
But the generated TypeScript code looks like:
export function ReadRaidMetadataResponseV1FromJSONTyped(json: any, ignoreDiscriminator: boolean): ReadRaidMetadataResponseV1 {
if ((json === undefined) || (json === null)) {
return json;
}
switch (json['metadataSchema']) {
case 'closed-metadata-schema-v1':
return {...ClosedMetadataSchemaV1FromJSONTyped(json, true), metadataSchema: 'closed-metadata-schema-v1'};
case 'raido-metadata-schema-v1':
return {...PublicMetadataSchemaV1FromJSONTyped(json, true), metadataSchema: 'raido-metadata-schema-v1'};
default:
throw new Error(`No variant of ReadRaidMetadataResponseV1 exists with 'metadataSchema=${json['metadataSchema']}'`);
}
}
So it always fails, because the TypeScript code only knows about the (correct) raido-metadata-schema-v1
mapping value.
The repo with the full code is publicly visible, you can find the full openapi
YAML files at:
https://github.com/au-research/raido-v2/blob/b06c08349a58b4559768963c5e3fbb81cc2c2f28/api-svc/idl-raid-v2/src/shared.yaml#L36
The question:
How can I structure my openapi
definition, or what flags do I need to use so that the generated typescript-fetch
code can actually use the API served by the JavaSpring
generator?
The easy "workaround" answer (*) to this question is that you must set the mapping value be the same as the name of the type that is being mapped to. That is:
discriminator:
propertyName: metadataSchema
mapping:
raido-metadata-schema-v1: '#/components/schemas/PublicMetadataSchemaV1'
closed-metadata-schema-v1: '#/components/schemas/ClosedMetadataSchemaV1'
has to be re-written as:
discriminator:
propertyName: metadataSchema
mapping:
PublicMetadataSchemaV1: '#/components/schemas/PublicMetadataSchemaV1'
ClosedMetadataSchemaV1: '#/components/schemas/ClosedMetadataSchemaV1'
(*) - "Easy" but annoying and potentially awkward. If anyone posts an answer that allows the mapping values to be arbitrary instead of being forced to match the type name - I will gladly mark it as the correct answer.