Здесь я рассказывал о том, как настроить генерацию в приложении.
В этой статье покажу несколько пример сгенерированного года с использованием композиции и полиморфизма 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 можно комбинировать и составлять достаточно сложные конструкции для решения ваших задач.
Иногда одного и того же необходимого результата генерации можно добиться разными способами описания спецификации.
