Генерация контрактов OpenApi или прикладной API first: oneOf, anyOf, allOf

от автора

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

Иногда одного и того же необходимого результата генерации можно добиться разными способами описания спецификации.


ссылка на оригинал статьи https://habr.com/ru/articles/774928/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *