No polymorphism for responses and non-primitive fields in generated OpenApi spec
According to swagger docs polymorphism can be supported using oneOf and discriminator. This way we can achieve polymorphic API responses and polymorphic properties on objects.
However, looks like smallrye open api does not have ability to generate such open api schema. Automatically when mp.openapi.extensions.smallrye.auto-inheritance=BOTH is enabled. It is only possible by manually adding polymorphism to @Schema on fields and @ApiResponse
Suppose we have the following simple class hierarchy. Note: type on BaseClass is used as discriminator.
@Schema
public class BaseClass {
private String baseField;
private String type;
private FieldBaseClass someObjectField;
}
@Schema
public class FieldBaseClass {
private String baseFieldClassField;
}
@Schema
public class FirstLevelFieldClass extends FieldBaseClass {
private String firstLevelFieldClassField;
}
@Schema
public class FirstLevelSubclass extends BaseClass{
private String firstLevelClassField;
}
@Schema
public class SecondLevelSubclass extends FirstLevelSubclass{
private String secondLevelClassField;
}
Also, there is a simple Jax-Rs endpoint
@Path("/hello")
public class ExampleResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public BaseClass hello() {
return new BaseClass();
}
}
The resulting OpenApi spec is as follows (with mp.openapi.extensions.smallrye.auto-inheritance=BOTH)
---
openapi: 3.0.3
info:
title: demo API
version: 1.0-SNAPSHOT
paths:
/hello:
get:
tags:
- Example Resource
responses:
"200":
description: OK
content:
text/plain:
schema:
$ref: "#/components/schemas/BaseClass"
components:
schemas:
BaseClass:
type: object
properties:
baseField:
type: string
type:
type: string
someObjectField:
$ref: "#/components/schemas/FieldBaseClass"
FieldBaseClass:
type: object
properties:
baseFieldClassField:
type: string
FirstLevelFieldClass:
allOf:
- $ref: "#/components/schemas/FieldBaseClass"
- type: object
properties:
firstLevelFieldClassField:
type: string
FirstLevelSubclass:
allOf:
- $ref: "#/components/schemas/BaseClass"
- type: object
properties:
firstLevelClassField:
type: string
SecondLevelSubclass:
allOf:
- $ref: "#/components/schemas/FirstLevelSubclass"
- type: object
properties:
secondLevelClassField:
type: string
Expected result:
- Response for /hello must contain polymorphic oneOf schema with discriminator and mapping
- someObjectField of class BaseClass has a polymorphic oneOf schema with discriminator and mapping
- User of library can specify discriminator property on base class
- User of library can specify discriminator mapping for each subclass. Then this mapping will be used to generate syncthetic polymorphic schema
For example, smallrye should add syncthetic polymorphic schema to schemas, which can look like this:
---
openapi: 3.0.3
info:
title: demo API
version: 1.0-SNAPSHOT
paths:
/hello:
get:
tags:
- Example Resource
responses:
"200":
description: OK
content:
text/plain:
schema:
$ref: "#/components/schemas/PolymorphicBaseClass"
components:
schemas:
PolymorphicBaseClass:
type: object
oneOf:
- $ref: "#/components/schemas/BaseClass"
- $ref: "#/components/schemas/FirstLevelSubclass"
- $ref: "#/components/schemas/SecondLevelSubclass"
discriminator:
propertyName: type
mapping:
baseType: "#/components/schemas/BaseClass"
firstType: "#/components/schemas/FirstLevelSubclass"
secondType: "#/components/schemas/SecondLevelSubclass"
PolymorphicFieldBaseClass:
type: object
oneOf:
- $ref: "#/components/schemas/FieldBaseClass"
- $ref: "#/components/schemas/FirstLevelFieldClass"
discriminator:
propertyName: type
mapping:
baseFieldType: "#/components/schemas/FieldBaseClass"
firstFieldType: "#/components/schemas/FirstLevelFieldClass"
BaseClass:
type: object
properties:
baseField:
type: string
type:
type: string
someObjectField:
$ref: "#/components/schemas/PolymorphicFieldBaseClass"
FieldBaseClass:
type: object
properties:
baseFieldClassField:
type: string
FirstLevelFieldClass:
allOf:
- $ref: "#/components/schemas/FieldBaseClass"
- type: object
properties:
firstLevelFieldClassField:
type: string
FirstLevelSubclass:
allOf:
- $ref: "#/components/schemas/BaseClass"
- type: object
properties:
firstLevelClassField:
type: string
SecondLevelSubclass:
allOf:
- $ref: "#/components/schemas/FirstLevelSubclass"
- type: object
properties:
secondLevelClassField:
type: string
Such ploymorphic API spec is extremely important for some forontend TS consumers. For example, popylar rtk-query lib for frontend can not generate polymorphic API without explicit polymorphic typs declaration (using oneOf and discriminator). In addition, polymorphic API gives human consumers ability to define class hierarchy and used discriminator types just by looking at spec.
Your JAX-RS endpoint is returning "text/plain". How would the BaseClass be encoded to text/plain? And if the method implementation would in fact return a FirstLevelSubclass: how would that be encoded and how would a client tell those encodings apart (i.e. how would the client know that the returned object is in fact a FirstLevelSubclass).
smallrye already supports exactly what you are looking for with JSON. Just return "application/json" from your endpoint and use Jackson annotations (JsonTypeInfo, JsonSubTypes, etc.) to annotate which subtypes are allowed for a base type and which property within the JSON can be used by the client to tell different subtypes apart. smallrye will use those to generate the appropriate oneOf and discriminator information in the yaml.