Спадщина і склад Своггера


82

У моєму "спрощеному" API всі відповіді виводяться ( успадковуються ) з базового класу "відповіді". Клас відповіді складається із заголовка, заповненого метаданими, та тіла, що містить основні дані, які користувач запитує. Відповідь (у форматі JSON) викладена таким чином, що всі метадані знаходяться на першому "шарі", а тіло є єдиним атрибутом, що називається "тіло" як таке

response
|--metadata attribute 1 (string/int/object)
|--metadata attribute 2 (string/int/object)
|--body (object)
    |--body attribute 1 (string/int/object)
    |--body attribute 2 (string/int/object)

Я спробував визначити це співвідношення у хитрому з наступним JSON:

{
    ...
    "definitions": {
        "response": {
            "allOf": [
                {
                    "$ref": "#/definitions/response_header"
                },
                {
                    "properties": {
                        "body": {
                            "description": "The body of the response (not metadata)",
                            "schema": {
                                "$ref": "#/definitions/response_body"
                            }
                        }
                    }
                }
            ]
        },
        "response_header": {
            "type": "object",
            "required": [
                "result"
            ],
            "properties": {
                "result": {
                    "type": "string",
                    "description": "value of 'success', for a successful response, or 'error' if there is an error",
                    "enum": [
                        "error",
                        "success"
                    ]
                },
                "message": {
                    "type": "string",
                    "description": "A suitable error message if something went wrong."
                }
            }
        },
        "response_body": {
            "type": "object"
        }
    }
}

Потім я намагаюся створити різні відповіді, створюючи різні класи body / header, які успадковуються від body / header, а потім створюю дочірні класи відповідей, які складаються з відповідних класів header / body (показано у вихідному коді внизу). Однак я впевнений, що або це неправильний спосіб робити щось, або що моє впровадження неправильне. Я не зміг знайти приклад успадкування в специфікації swagger 2.0 (показано нижче), але знайшов приклад композиції .

введіть тут опис зображення

Я майже впевнений, що цей "дискримінатор" повинен зіграти велику роль, але не впевнений, що мені потрібно робити.

Питання

Хтось може показати мені, як передбачається реалізувати композицію + успадкування в swagger 2.0 (JSON), бажано, "виправивши" мій приклад коду нижче. Також було б чудово, якби я міг вказати клас ErrorResponse, який успадковується від відповіді, де атрибуту "result" у заголовку завжди встановлено значення "error".

{
    "swagger": "2.0",
    "info": {
        "title": "Test API",
        "description": "Request data from the system.",
        "version": "1.0.0"
    },
    "host": "xxx.xxx.com",
    "schemes": [
        "https"
    ],
    "basePath": "/",
    "produces": [
        "application/json"
    ],
    "paths": {
        "/request_filename": {
            "post": {
                "summary": "Request Filename",
                "description": "Generates an appropriate filename for a given data request.",
                "responses": {
                    "200": {
                        "description": "A JSON response with the generated filename",
                        "schema": {
                            "$ref": "#/definitions/filename_response"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "response": {
            "allOf": [
                {
                    "$ref": "#/definitions/response_header"
                },
                {
                    "properties": {
                        "body": {
                            "description": "The body of the response (not metadata)",
                            "schema": {
                                "$ref": "#/definitions/response_body"
                            }
                        }
                    }
                }
            ]
        },
        "response_header": {
            "type": "object",
            "required": [
                "result"
            ],
            "properties": {
                "result": {
                    "type": "string",
                    "description": "value of 'success', for a successful response, or 'error' if there is an error",
                    "enum": [
                        "error",
                        "success"
                    ]
                },
                "message": {
                    "type": "string",
                    "description": "A suitable error message if something went wrong."
                }
            }
        },
        "response_body": {
            "type": "object"
        },
        "filename_response": {
            "extends": "response",
            "allOf": [
                {
                    "$ref": "#definitions/response_header"
                },
                {
                    "properties": {
                        "body": {
                            "schema": {
                                "$ref": "#definitions/filename_response_body"
                            }
                        }
                    }
                }
            ]
        },
        "filename_response_body": {
            "extends": "#/definitions/response_body",
            "properties": {
                "filename": {
                    "type": "string",
                    "description": "The automatically generated filename"
                }
            }
        }
    }
}

Оновлення схеми

Щоб спробувати пояснити, що я хочу, я створив дуже базову діаграму нижче, яка має на меті показати, що всі відповіді є екземплярами об'єкта "відповідь", який був побудований (композицією) за допомогою будь-якої комбінації об'єктів response_header і response_body. Об'єкти response_header і response_body можуть бути розширені та вставлені в будь-який об'єкт відповіді, що робиться у випадку з ім'ям файлу_відповідь, який використовує дочірнє ім'я файлу_відповідь_тіла базового класу response_body. Як помилки, так і успішні відповіді використовують об'єкт "відповідь".

введіть тут опис зображення


1
Там є зразком для композиції, але це так погано , що не варто поділитися. Я попрацюю над тим, як має виглядати ваша специфікація. Майте на увазі, що користувальницький інтерфейс наразі його не підтримує, але він буде підтримуватися, коли буде доступна повна підтримка 2.0.
Рон,

1
І перед тим, як зануритися, ще одне - ви шукаєте склад чи спадщину? Склад в основному говорить I have the properties of X and my own properties.. Спадщина передбачає стосунки X is my parent. I have its properties and my own.. Спадщина корисна, якщо ви хочете сказати, що певний набір моделей застосовується батьківського, який використовується.
Рон

1
На цьому прикладі я сподівався продемонструвати використання спадщини та композиції в одному русі. Очевидно, я усвідомлюю, що можна було б легко використовувати будь-яку з них самостійно, але в цьому випадку всі відповіді - це діти базового класу "відповідь". А клас відповіді «складається» з двох інших об’єктів, заголовка та тіла.
Programster

2
Можливо, я не зрозумів. Спадщина є продовженням композиції. Якщо є спадщина, є склад. Якщо є склад, не обов’язково успадкування. Крім того, у вашому зразку модель "відповіді" ніде не використовується. Чи варто це ігнорувати і просто показати, як це повинно виглядати?
Рон

ах, не усвідомлював, що відносини між спадщиною та складом. Тож використовуйте спадщину, щоб показати обидва. Стосовно моделі відповіді, яка не використовується, її слід використовувати з "extends" у дочірньому файлі name_response, на який відповідає запит.
Programster

Відповіді:


114

Як початківець у розбиранні, я не вважаю офіційну документацію про поліморфізм та склад легкою для сприйняття, оскільки їй бракує прикладу . Коли я шукав у Мережі, є багато хороших прикладів, які посилаються на хитрість 1.2, коли вона extendsбула дійсною.

Для swagger 2.0 я знайшов хороший приклад у джерелах специфікацій swagger на github через цю групу google

На основі вищезазначених джерел, ось короткий приклад дійсного успадкування в YAML:

definitions:
  Pet:
    discriminator: petType
    required:
      - name
      - petType # required for inheritance to work
    properties:
      name: 
        type: string
      petType:
        type: string
  Cat:
    allOf:
      - $ref: '#/definitions/Pet' # Cat has all properties of a Pet
      - properties: # extra properties only for cats
          huntingSkill:
            type: string
            default: lazy
            enum:
              - lazy
              - aggressive
  Dog:
    allOf:
      - $ref: '#/definitions/Pet' # Dog has all properties of a Pet
      - properties: # extra properties only for dogs
          packSize:
            description: The size of the pack the dog is from
            type: integer

Справді дякую! Це працює для мене. В editor.swagger.io, я бачу невелику помилку: у розділі моделі я бачу Petмодель кілька разів. Зміст цих моделей добре. Помилкові лише імена.
schellingerht

@schellingerht У editor2.swagger.ioцій проблемі ви не побачите
Шиплу Мокаддім

Єдине питання, яке я виявив за допомогою цього способу визначення спадщини, полягає в тому, що властивість petType трохи марно в створеному класі. Буде порожньо. Але принаймні це генерує ієрархію класів, як я думав. Дякую!
xarlymg89

Для того, щоб створити спадщину json, як зазначено вище, вам потрібно анотувати свої класи батьків та дітей так: @ApiModel (дискримінатор = "тип", підтипи = {Cat.class, Dog.class}) відкритий абстрактний клас Animal {} @ ApiModel (parent = Animal.class) public calss Cat extends Animal {}
Джанет

Чи використовується дискримінатор лише тоді, коли ми реалізуємо Інтерфейс Pet, як щодо того, якщо клас A поширює клас B, чи слід також використовувати його? Дякую
Bionix1441

23

Я виявив, що композиція чудово працює навіть без визначення discriminator.

Наприклад, база Response:

definitions:
  Response:
    description: Default API response
    properties:
      status:
        description: Response status `success` or `error`
        type: string
        enum: ["success", "error"]
      error_details:
        description: Exception message if called
        type: ["string", "object", "null"]
      error_message:
        description: Human readable error message
        type: ["string", "null"]
      result:
        description: Result body
        type: ["object", "null"]
      timestamp:
        description: UTC timestamp in ISO 8601 format
        type: string
    required:
      - status
      - timestamp
      - error_details
      - error_message
      - result

Відображається як:

Візуалізація відповіді

І ми можемо розширити його, щоб уточнити власну схему resultполя:

  FooServiceResponse:
    description: Response for Foo service
    allOf:
      - $ref: '#/definitions/Response'
      - properties:
          result:
            type: object
            properties:
              foo_field:
                type: integer
                format: int32
              bar_field:
                type: string
        required:
          - result

І це буде правильно відображено як:

Візуалізація FooServiceResponse

Зауважте, цього allOfдостатньо, щоб це працювало, і жодне discriminatorполе не використовується. Це добре, тому що це працює, і це важливо, оскільки, на мою думку, інструменти зможуть генерувати код без discriminatorполя.


Я також використовував allOf, але якось у openapi.yaml, я виявляю, що підкласи містять властивості супер-класу надлишковим способом, чи правильно?
Bionix1441

9

Всі відповіді тут уже відмінні, але я просто хочу додати незначну примітку про склад проти спадщини . Згідно зі специфікацією Swagger / OpenAPI , для реалізації композиціїallOf достатньо використання властивості, як правильно вказує @oblalex . Однак для реалізації успадкування потрібно використовувати allOfwith discriminator, як у прикладі @ TomaszSętkowski .

Крім того, я знайшов ще кілька прикладів Swagger як щодо складу, так і для успадкування в API Handyman. Вони є частиною чудової серії підручників Swagger / OpenAPI від Арно Лоре, яку, на мою думку, кожен повинен перевірити.


1
@ Хоча розміщення відповідних посилань є хорошим початком, щоб насправді бути корисною відповіддю, ви також повинні процитувати відповідний текст, який можна знайти за посиланням. Відповіді лише на посилання не рекомендуються, оскільки посилання часто мертві.
Stijn de Witt

3

Стандартний приклад Swagger 2.0, яким ви поділилися, зображує взаємозв'язок композиції, зокрема він фіксує "це свого роду" відношення супер-тип / підтип, однак це не є поліморфізмом сам по собі.

Було б, якби ви могли посилатися на базове визначення Pet як вхідний параметр, а потім вибрати Cat або ввести об’єкт Cat JSON як значення для вхідного запиту і мати це прийнятним для інтерфейсу Swagger.

Я не міг змусити це безпосередньо працювати.

Найкраще, що я міг запрацювати, було встановити значення trueProperties на базовому об'єкті (наприклад, Pet), вказати Pet, використовуючи посилання на вказівник JSON як схему введення, і, нарешті, скопіювати та вставити мій об'єкт значення Cat JSON в інтерфейс Swagger. Оскільки додаткові властивості дозволені, користувальницький інтерфейс Swagger генерує дійсний корисний набір вхідного запиту.


Ви не робите поліморфізму по дроту (або виставляєте свої сутності даних) .. окрім випадків, коли ви хочете щільно з'єднати певний хакер, щоб він працював.
user1496062

Поліморфізм увімкнено внаслідок успадкування, але не потрібно використовувати спадкування. Логічно, що спадкування - це відносини "є", тоді як композиція - "має". Межа між ними може бути розмитою залежно від мови реалізації та випадків використання домену. Але це вихідний пункт. Крім того, дискримінатор дозволяє десериалізувати поліморфні типи. Існують інші підходи (наприклад, включаючи імена класів Java). Але, погодьтеся, вони можуть бути важкими та не портативними. Наприклад, що робитиме клієнт python з іменами класів Java?
Чарлі Рейтцель,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.