Здесь я рассказывал о том, как настроить генерацию в приложении.
В этой статье покажу несколько пример сгенерированного года с использованием композиции и полиморфизма openapi. Здесь можно почитать небольшой раздел документации.
1. Репозиторий
Пример спецификации и сгенерированного кода можно найти здесь. Кроме того по тексту будут оставлены ссылки на конкретные сгенерированные классы и строки в спецификации.
2. discriminator
Здесь можно почитать что такое дискриминатор в концепции openapi. Если вкратце - дискриминатор это именно та вещь, которая позволяет управлять генерацией кода и получать нужный результат при использовании полиморфизма в спецификации.
Далее будут показаны примеры сгенерированного кода на очень простых моделях, без дискриминаторов и с ними.
3.1 OneOf без дискриминатора
Как это выглядит в спецификации:
OneOfObjectWithoutDiscriminator:
oneOf:
- $ref: '#/components/schemas/OneOfObjectWithoutDiscriminatorFirstProperty'
- $ref: '#/components/schemas/OneOfObjectWithoutDiscriminatorSecondProperty'
OneOfObjectWithoutDiscriminatorFirstProperty:
type: object
required:
- someProperty
properties:
someProperty:
type: string
OneOfObjectWithoutDiscriminatorSecondProperty:
type: object
required:
- anotherProperty
properties:
anotherProperty:
type: string
Мы создаём объект, в котором у нас должно лежать два строковых поля. Что мы получаем после генерации: OneOfObjectWithoutDiscriminator, OneOfObjectWithoutDiscriminatorFirstProperty, OneOfObjectWithoutDiscriminatorSecondProperty
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public interface OneOfObjectWithoutDiscriminator {
}
/**
* OneOfObjectWithoutDiscriminatorFirstProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithoutDiscriminatorFirstProperty implements OneOfObjectWithoutDiscriminator {
private String someProperty;
...
/**
* OneOfObjectWithoutDiscriminatorSecondProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithoutDiscriminatorSecondProperty implements OneOfObjectWithoutDiscriminator {
private String anotherProperty;
...
При получении запроса приложением такая конструкция будет ломаться, потому что мы не сможем определить какой именно реализацией является входящий json.
3.2 OneOf с дискриминатором
Как это выглядит в спецификации:
OneOfObjectWithDiscriminator:
oneOf:
- $ref: '#/components/schemas/OneOfObjectWithDiscriminatorFirstProperty'
- $ref: '#/components/schemas/OneOfObjectWithDiscriminatorSecondProperty'
discriminator:
propertyName: propertyType
mapping:
first: '#OneOfObjectWithDiscriminatorFirstProperty'
second: '#OneOfObjectWithDiscriminatorSecondProperty'
OneOfObjectWithDiscriminatorFirstProperty:
type: object
required:
- propertyType
- someProperty
properties:
propertyType:
type: string
someProperty:
type: string
OneOfObjectWithDiscriminatorSecondProperty:
type: object
required:
- propertyType
- anotherProperty
properties:
propertyType:
type: string
anotherProperty:
type: string
Мы видим что здесь добавилось обязательное поле, значение которого будет использоваться дискриминатором для определения класса который нужно использовать при получении запроса.
Что мы получаем после генерации: OneOfObjectWithDiscriminator, OneOfObjectWithDiscriminatorFirstProperty, OneOfObjectWithDiscriminatorSecondProperty
@JsonIgnoreProperties(
value = "propertyType", // ignore manually set propertyType, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the propertyType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "propertyType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorFirstProperty.class, name = "first"),
@JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorSecondProperty.class, name = "second"),
@JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorFirstProperty.class, name = "OneOfObjectWithDiscriminatorFirstProperty"),
@JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorSecondProperty.class, name = "OneOfObjectWithDiscriminatorSecondProperty")
})
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public interface OneOfObjectWithDiscriminator {
public String getPropertyType();
}
/**
* OneOfObjectWithDiscriminatorFirstProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithDiscriminatorFirstProperty implements OneOfObjectWithDiscriminator {
private String propertyType;
private String someProperty;
...
/**
* OneOfObjectWithDiscriminatorSecondProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithDiscriminatorSecondProperty implements OneOfObjectWithDiscriminator {
private String propertyType;
private String anotherProperty;
...
И вот здесь начинает работать обвязка дискриминатором, которая превращается в аннотации JsonSubTypes. Такую генерацию мы уже можем безопасно использовать в нашем приложении.
4.1 AllOf без дискриминатора
Как это выглядит в спецификации:
AllOfObjectWithoutDiscriminator:
allOf:
- $ref: '#/components/schemas/AllOfObjectWithoutDiscriminatorFirstProperty'
- $ref: '#/components/schemas/AllOfObjectWithoutDiscriminatorSecondProperty'
AllOfObjectWithoutDiscriminatorFirstProperty:
type: object
required:
- someProperty
properties:
someProperty:
type: string
AllOfObjectWithoutDiscriminatorSecondProperty:
type: object
required:
- anotherProperty
properties:
anotherProperty:
type: string
Результат генерации: AllOfObjectWithoutDiscriminator, AllOfObjectWithoutDiscriminatorFirstProperty, AllOfObjectWithoutDiscriminatorSecondProperty
/**
* AllOfObjectWithoutDiscriminator
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithoutDiscriminator {
private String someProperty;
private String anotherProperty;
...
/**
* AllOfObjectWithoutDiscriminatorFirstProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithoutDiscriminatorFirstProperty {
private String someProperty;
...
/**
* AllOfObjectWithoutDiscriminatorSecondProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithoutDiscriminatorSecondProperty {
private String anotherProperty;
...
Мы видим что AllOfObjectWithoutDiscriminator является уже не интерфейсом а классом, и содержит в себе сразу оба поля из указанных allOf компонентов.
4.2 AllOf с дискриминатором
Как это выглядит в спецификации:
AllOfObjectWithDiscriminator:
allOf:
- $ref: '#/components/schemas/AllOfObjectWithDiscriminatorFirstProperty'
- $ref: '#/components/schemas/AllOfObjectWithDiscriminatorSecondProperty'
discriminator:
propertyName: propertyType
mapping:
first: '#AllOfObjectWithDiscriminatorFirstProperty'
second: '#AllOfObjectWithDiscriminatorSecondProperty'
AllOfObjectWithDiscriminatorFirstProperty:
type: object
required:
- propertyType
- someProperty
properties:
propertyType:
type: string
someProperty:
type: string
AllOfObjectWithDiscriminatorSecondProperty:
type: object
required:
- propertyType
- anotherProperty
properties:
propertyType:
type: string
anotherProperty:
type: string
Результат генерации: AllOfObjectWithDiscriminator, AllOfObjectWithDiscriminatorFirstProperty, AllOfObjectWithDiscriminatorSecondProperty
/**
* AllOfObjectWithDiscriminator
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithDiscriminator {
private String propertyType;
private String someProperty;
private String anotherProperty;
...
/**
* AllOfObjectWithDiscriminatorFirstProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithDiscriminatorFirstProperty {
private String propertyType;
private String someProperty;
...
/**
* AllOfObjectWithDiscriminatorSecondProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithDiscriminatorSecondProperty {
private String propertyType;
private String anotherProperty;
...
Мы видим что генерация с дискриминатором не отличается от генерации без дискриминатора. Поэтому в случае с allOf, который является инструментом композиции в рамках openapi, можно пользоваться как тем, так и другим вариантом.
5.1 AnyOf без дискриминатора
Как это выглядит в спецификации:
AnyOfObjectWithoutDiscriminator:
anyOf:
- $ref: '#/components/schemas/AnyOfObjectWithoutDiscriminatorFirstProperty'
- $ref: '#/components/schemas/AnyOfObjectWithoutDiscriminatorSecondProperty'
AnyOfObjectWithoutDiscriminatorFirstProperty:
type: object
required:
- someProperty
properties:
someProperty:
type: string
AnyOfObjectWithoutDiscriminatorSecondProperty:
type: object
required:
- anotherProperty
properties:
anotherProperty:
type: string
Результат генерации: AnyOfObjectWithoutDiscriminator, AnyOfObjectWithoutDiscriminatorFirstProperty, AnyOfObjectWithoutDiscriminatorSecondProperty
/**
* AnyOfObjectWithoutDiscriminator
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithoutDiscriminator {
private String someProperty;
private String anotherProperty;
...
/**
* AnyOfObjectWithoutDiscriminatorFirstProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithoutDiscriminatorFirstProperty {
private String someProperty;
...
/**
* AnyOfObjectWithoutDiscriminatorSecondProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithoutDiscriminatorSecondProperty {
private String anotherProperty;
...
5.2 AnyOf с дискриминатором
Как это выглядит в спецификации:
AnyOfObjectWithDiscriminator:
anyOf:
- $ref: '#/components/schemas/AnyOfObjectWithDiscriminatorFirstProperty'
- $ref: '#/components/schemas/AnyOfObjectWithDiscriminatorSecondProperty'
discriminator:
propertyName: PropertyType
mapping:
first: '#AnyOfObjectWithDiscriminatorFirstProperty'
second: '#AnyOfObjectWithDiscriminatorSecondProperty'
AnyOfObjectWithDiscriminatorFirstProperty:
type: object
required:
- propertyType
- someProperty
properties:
propertyType:
type: string
someProperty:
type: string
AnyOfObjectWithDiscriminatorSecondProperty:
type: object
required:
- propertyType
- anotherProperty
properties:
propertyType:
type: string
anotherProperty:
type: string
Результат генерации: AnyOfObjectWithDiscriminator, AnyOfObjectWithDiscriminatorFirstProperty, AnyOfObjectWithDiscriminatorSecondProperty
/**
* AnyOfObjectWithDiscriminator
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@JsonIgnoreProperties(
value = "PropertyType", // ignore manually set PropertyType, it will be automatically generated by Jackson during serialization
allowSetters = true // allows the PropertyType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "PropertyType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorFirstProperty.class, name = "first"),
@JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorSecondProperty.class, name = "second"),
@JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorFirstProperty.class, name = "AnyOfObjectWithDiscriminatorFirstProperty"),
@JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorSecondProperty.class, name = "AnyOfObjectWithDiscriminatorSecondProperty")
})
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithDiscriminator {
private String propertyType;
private String someProperty;
private String anotherProperty;
...
/**
* AnyOfObjectWithDiscriminatorFirstProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithDiscriminatorFirstProperty {
private String propertyType;
private String someProperty;
...
/**
* AnyOfObjectWithDiscriminatorSecondProperty
*/
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithDiscriminatorSecondProperty {
private String propertyType;
private String anotherProperty;
...
Результат аналогичный с oneOf. При это если мы используем oneOf, валидация ограничивает входящий запрос до одного из описанных вариантов, в то время как anyOf позволяет принять от одного до всех вариантов.
6. P.S.
AnyOf, AllOf, OneOf можно комбинировать и составлять достаточно сложные конструкции для решения ваших задач.
Иногда одного и того же необходимого результата генерации можно добиться разными способами описания спецификации.
AlexSteelax
Господи, спасибо тебе за протобаф.
dph
Да ладно, в протобафе есть Any и несовместимость между версиями, это еще хуже.