Swagger Vererbung und Zusammensetzung

80

In meiner "vereinfachten" API werden alle Antworten von einer Basisklasse "Antwort" abgeleitet ( geerbt ). Die Antwortklasse besteht aus einem mit Metadaten gefüllten Header und dem Textkörper, der die vom Benutzer angeforderten Kerndaten enthält. Die Antwort (in JSON) ist so angelegt, dass sich alle Metadaten auf der ersten "Ebene" befinden und der Körper ein einzelnes Attribut ist, das als solcher "Körper" bezeichnet wird

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)

Ich habe versucht, diese Beziehung in Prahlerei mit dem folgenden JSON zu definieren:

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

Ich versuche dann, verschiedene Antworten zu erstellen, indem ich die verschiedenen Body- / Header-Klassen erstelle, die von Body / Header erben, und dann untergeordnete Antwortklassen erstelle, die aus den relevanten Header- / Body-Klassen bestehen (siehe Quellcode unten). Ich bin mir jedoch sicher, dass dies entweder die falsche Vorgehensweise ist oder dass meine Implementierung falsch ist. Ich konnte kein Beispiel für die Vererbung in der Swagger 2.0-Spezifikation (siehe unten) finden, habe aber ein Beispiel für die Zusammensetzung gefunden .

Geben Sie hier die Bildbeschreibung ein

Ich bin mir ziemlich sicher, dass dieser "Diskriminator" eine große Rolle spielt, bin mir aber nicht sicher, was ich tun muss.

Frage

Könnte mir jemand zeigen, wie man Komposition + Vererbung in Swagger 2.0 (JSON) implementieren soll, vorzugsweise indem er meinen Beispielcode unten "repariert". Es wäre auch großartig, wenn ich eine ErrorResponse-Klasse angeben könnte, die von einer Antwort erbt, bei der das Attribut "result" im Header immer auf "error" gesetzt ist.

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

Diagrammaktualisierung

Um zu klären, was ich will, habe ich das folgende grundlegende Diagramm erstellt, das zeigen soll, dass alle Antworten Instanziierungen des "Antwort" -Objekts sind, die von (Komposition) unter Verwendung einer beliebigen Kombination von response_header- und response_body-Objekten erstellt wurden. Die Objekte response_header und response_body können erweitert und in jedes Antwortobjekt eingefügt werden. Dies erfolgt im Fall einer Dateinamenantwort, die das untergeordnete Element filename_response_body der Basisklasse response_body verwendet. Sowohl fehlerhafte als auch erfolgreiche Antworten verwenden das Objekt "response".

Geben Sie hier die Bildbeschreibung ein

Programmierer
quelle
1
Es gibt ein Beispiel für die Komposition, aber es ist so schlecht, dass es sich nicht lohnt, es zu teilen. Ich werde daran arbeiten, wie Ihre Spezifikation aussehen soll. Beachten Sie, dass die Benutzeroberfläche dies derzeit nicht unterstützt, dies jedoch der Fall ist, wenn die vollständige Unterstützung für 2.0 verfügbar ist.
Ron
1
Und bevor ich eintauche, noch eine Sache - suchen Sie nach Komposition oder Erbschaft? Zusammensetzung sagt im Grunde I have the properties of X and my own properties.. Vererbung deutet auf eine Beziehung hin X is my parent. I have its properties and my own.. Vererbung ist nützlich, wenn Sie sagen möchten, dass ein bestimmter Satz von Modellen für das verwendete übergeordnete Modell gilt.
Ron
1
Ich hatte eher gehofft, mit diesem Beispiel die Verwendung von Vererbung und Komposition auf einmal demonstrieren zu können . Natürlich ist mir klar, dass man beides leicht alleine verwenden kann, aber in diesem Fall sind alle Antworten Kinder der Basisklasse "Antwort". Und die Antwortklasse besteht aus zwei anderen Objekten, dem Header und dem Body.
Programster
2
Ich war vielleicht nicht klar. Vererbung ist eine Erweiterung der Zusammensetzung. Wenn es Vererbung gibt, gibt es Komposition. Wenn es Komposition gibt, gibt es nicht unbedingt Vererbung. Außerdem wird in Ihrem Beispiel das "Antwort" -Modell nirgendwo verwendet. Soll ich das ignorieren und nur zeigen, wie es aussehen soll?
Ron
ah, habe diese Beziehung zwischen Vererbung und Zusammensetzung nicht erkannt. Verwenden Sie also die Vererbung, um beide anzuzeigen. In Bezug auf das nicht verwendete Antwortmodell sollte es mit den "Erweitert" im untergeordneten Element "Dateiname_Response" verwendet werden, mit dem die Anforderung antwortet.
Programster

Antworten:

112

Als Anfänger in der Prahlerei finde ich die offizielle Dokumentation über Polimorphismus und Komposition nicht leicht zu verstehen, weil es kein Beispiel gibt . Als ich im Internet gesucht habe, gibt es viele gute Beispiele für Swagger 1.2, als extendses gültig war.

Für Swagger 2.0 habe ich über diese Google-Gruppe ein gutes Beispiel in Swagger-Spezifikationsquellen auf Github gefunden

Basierend auf den oben genannten Quellen finden Sie hier ein kurzes gültiges Vererbungsbeispiel in 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
Tomasz Sętkowski
quelle
Wirklich danke! Das funktioniert bei mir. In editor.swagger.iosehe ich einen kleinen Fehler: Im PetModellbereich sehe ich das Modell mehrmals. Der Inhalt dieser Modelle ist in Ordnung. Nur die Namen sind falsch.
schellingerht
@schellingerht In editor2.swagger.ioSie werden dieses Problem nicht sehen
Shiplu Mokaddim
Das einzige Problem, das ich bei dieser Art der Definition der Vererbung festgestellt habe, ist, dass die petType-Eigenschaft in der generierten Klasse etwas nutzlos ist. Es wird leer sein. Aber zumindest erzeugt es die Klassenhierarchie so, wie ich es mir vorgestellt habe. Vielen Dank!
Xarlymg89
Um die Vererbung json wie oben zu erstellen, müssen Sie Ihre Eltern- und Kinderklassen wie folgt mit Anmerkungen versehen: @ApiModel (Diskriminator = "Typ", Subtypen = {Cat.class, Dog.class}) öffentliche abstrakte Klasse Animal {} @ ApiModel (parent = Animal.class) public calss Cat erweitert Animal {}
Janet
22

Ich habe festgestellt, dass die Komposition auch ohne Definition von gut funktioniert discriminator.

Zum Beispiel 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

Wird gerendert als:

Antwortvisualisierung

Und wir können es erweitern, um das benutzerdefinierte resultFeldschema zu verfeinern :

  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

Und es wird korrekt gerendert als:

FooServiceResponse-Visualisierung

Beachten Sie, dass dies allOfausreicht, damit dies funktioniert und kein discriminatorFeld verwendet wird. Das ist gut, weil es funktioniert und dies wichtig ist, da Tools meiner Meinung nach Code ohne discriminatorFeld generieren können .

Oblalex
quelle
8

Alle Antworten hier sind bereits ausgezeichnet, aber ich möchte nur eine kleine Anmerkung zu Komposition und Vererbung hinzufügen . Laut der Swagger / OpenAPI-Spezifikation reicht es aus, die Eigenschaft zu verwenden , um die Komposition zu implementieren allOf, wie @oblalex richtig hervorhebt . Um jedoch zu implementieren Vererbung , müssen Sie verwenden allOfmit discriminator, wie im Beispiel von @ TomaszSętkowski .

Außerdem habe ich bei API Handyman einige weitere Swagger-Beispiele für Zusammensetzung und Vererbung gefunden . Sie sind Teil einer exzellenten Swagger / OpenAPI-Tutorialserie von Arnaud Lauret, die meiner Meinung nach jeder ausprobieren sollte.

DynamicDispatch
quelle
1
@ Obwohl das Posten relevanter Links ein guter Anfang ist, um tatsächlich eine nützliche Antwort zu sein, sollten Sie auch den relevanten Text zitieren, der unter dem Link zu finden ist. Von Nur-Link-Antworten wird abgeraten, da Links häufig nicht mehr funktionieren.
Stijn de Witt
3

Das von Ihnen freigegebene Swagger 2.0-Standardbeispiel zeigt eine Kompositionsbeziehung, insbesondere erfasst es eine "ist eine Art" Supertyp / Subtyp-Beziehung, ist jedoch kein Polymorphismus an und für sich.

Wenn Sie auf die Basisdefinition von Pet als Eingabeparameter verweisen könnten, wählen Sie Cat aus oder geben Sie ein Cat-JSON-Objekt als Wert für die Eingabeanforderung ein und lassen Sie dies für die Swagger-Benutzeroberfläche akzeptabel sein.

Ich konnte das nicht direkt zum Laufen bringen.

Das Beste, was ich zum Arbeiten bringen konnte, war, zusätzliche Eigenschaften für das Basisobjekt (z. B. Pet) auf true zu setzen, Pet mithilfe der JSON-Zeigerreferenz als Eingabeschema anzugeben und schließlich mein Cat JSON-Wertobjekt zu kopieren und in die Swagger-Benutzeroberfläche einzufügen. Da die zusätzlichen Eigenschaften zulässig sind, hat die Swagger-Benutzeroberfläche eine gültige Eingabeanforderungsnutzlast generiert.

user6317389
quelle
Sie machen keinen Polymorphismus über den Draht (oder legen Ihre Datenentitäten offen). Es sei denn, Sie möchten eng mit dem spezifischen Hack koppeln, damit er funktioniert.
user1496062
Polymorphismus wird durch Vererbung aktiviert, ist jedoch nicht erforderlich, um Vererbung zu verwenden. Vererbung ist logischerweise eine "ist-eine" Beziehung, während Komposition eine "hat-eine" Beziehung ist. Die Grenze zwischen beiden kann je nach Implementierungssprache und Anwendungsfällen der Domäne verschwommen sein. Aber das ist der Ausgangspunkt. Fwiw, der Diskriminator ermöglicht die Deserialisierung polymorpher Typen. Es gibt andere Ansätze (z. B. einschließlich Java-Klassennamen). Aber, vereinbart, können diese klobig und nicht tragbar sein. Was macht beispielsweise ein Python-Client mit Java-Klassennamen?
Charlie Reitzel