web-dev-qa-db-fra.com

Héritage et composition Swagger

Dans mon API "simplifiée", toutes les réponses sont dérivées ( héritées ) d'une classe "réponse" de base. La classe de réponse est composée d'un en-tête rempli de métadonnées et du corps contenant les données de base demandées par l'utilisateur. La réponse (en JSON) est disposée de telle sorte que toutes les métadonnées se trouvent sur le premier "calque" et que le corps est un attribut unique appelé "corps" en tant que tel.

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)

J'ai essayé de définir cette relation en swagger avec le JSON suivant:

{
    ...
    "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"
        }
    }
}

J'essaie ensuite de créer différentes réponses en créant les différentes classes de corps/en-tête héritées du corps/en-tête, puis de créer des classes de réponses enfants composées des classes d'en-tête/de corps pertinentes (indiquées dans le code source en bas). Cependant, je suis certain que ce n'est pas la bonne façon de faire les choses ou que ma mise en œuvre est incorrecte. J'ai été incapable de trouver un exemple d'héritage dans spécification swagger 2. (ci-dessous), mais j'ai trouvé un exemple de composition .

enter image description here

Je suis à peu près certain que ce "discriminateur" a un rôle important à jouer, mais je ne suis pas sûr de ce que je dois faire.

Question

Est-ce que quelqu'un pourrait me montrer comment on est supposé implémenter composition + héritage dans swagger 2.0 (JSON), de préférence en "corrigeant" mon exemple de code ci-dessous. Ce serait également bien si je pouvais spécifier une classe ErrorResponse qui hérite de la réponse où l'attribut "result" dans l'en-tête est toujours défini sur "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"
                }
            }
        }
    }
}

Mise à jour du diagramme

Pour essayer de clarifier ce que je veux, j'ai créé le diagramme très basique ci-dessous qui vise à montrer que toutes les réponses sont des instanciations de l'objet "réponse" construit par (composition) en utilisant une combinaison quelconque des objets response_header et response_body. Les objets response_header et response_body peuvent être étendus et insérés dans n'importe quel objet de réponse, ce qui est fait dans le cas d'un filename_response qui utilise l'enfant filename_response_body de la classe de base de response_body. Les réponses d'erreur et de succès utilisent l'objet "réponse".

enter image description here

71
Programster

En tant que débutant, je ne trouve pas le documentation officielle sur le polimorphisme et la composition facile à comprendre, car il manque d’exemple Quand j'ai cherché sur le net, il y a beaucoup de bons exemples faisant référence à swagger 1.2 lorsque extends était valide.

Pour swagger 2.0 , j'ai trouvé un bon exemple dans sources swagger spec sur github via this google group

Sur la base des sources ci-dessus, voici un court exemple d'héritage valide en 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
100
Tomasz Sętkowski

J'ai trouvé que la composition fonctionne bien même sans définition de discriminator.

Par exemple, base 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

Est rendu comme:

Response visualization

Et nous pouvons l’étendre pour affiner le schéma personnalisé du champ 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

Et il sera correctement rendu comme:

FooServiceResponse visualization

Notez que allOf suffit pour que cela fonctionne et aucun champ discriminator n’est utilisé. C’est bien, car cela fonctionne et c’est important, car je pense que les outils seront capables de générer du code sans le champ discriminator.

20
oblalex

Toutes les réponses ici sont déjà excellentes, mais je veux juste ajouter une note mineure à propos de composition versus héritage . Selon la Swagger/OpenAPI Spec , pour mettre en œuvre une composition , utiliser la propriété allOf suffit, car @ oblalex souligne correctement . Cependant, pour implémenter l'héritage , vous devez utiliser allOf avec discriminator, comme dans l'exemple par @ TomaszSętkowski .

De plus, j'ai trouvé d'autres exemples Swagger de composition et héritage chez API Handyman. Ils font partie d'un excellente série de tutoriels Swagger/OpenAPI par Arnaud Lauret que je pense que tout le monde devrait vérifier.

8
DynamicDispatch

L'exemple standard Swagger 2.0 que vous avez partagé décrit une relation de composition, plus précisément, il capture une relation "est une sorte de" super-type/sous-type, mais il ne s'agit pas d'un polymorphisme en soi.

Ce serait le cas si vous pouviez référencer la définition de base de Pet en tant que paramètre d'entrée, puis choisir Cat ou entrer un objet JSON Cat comme valeur pour la demande d'entrée et le faire pour Swagger UI.

Je ne pouvais pas obtenir que cela fonctionne directement.

Le mieux que je pouvais obtenir était de définir additionalProperties sur true sur l'objet de base (par exemple, Pet), de spécifier Pet en utilisant la référence de pointeur JSON comme schéma d'entrée et enfin de copier-coller mon objet de valeur Cat JSON dans l'interface utilisateur Swagger. Puisque les propriétés supplémentaires sont autorisées, l’interface utilisateur de Swagger a généré une charge utile de demande d’entrée valide.

3
user6317389