Swagger Inheritance and Composition
Asked Answered
T

4

95

In my "simplified" API, all responses are derived (inherit) from a base "response" class. The response class is composed of a header filled with metadata, and the body which contains the core data the the user is requesting. The response (in JSON) is laid out such that all the metadata is on the first "layer" and the body is a single attribute called "body" as such

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)

I have tried to define this relationship in swagger with the following 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"
        }
    }
}

I then try to create different responses by creating the various body/header classes that inherit from body/header, and then create child response classes that are composed of the relevant header/body classes (shown in source code at bottom). However, I am certain that either this is the wrong way to do things, or that my implementation is incorrect. I have been unable to find an example of inheritance in the swagger 2.0 specification (shown below) but have found an example of composition.

enter image description here

I am pretty certain that this "discriminator" has a large part to play, but not sure what I need to do.

Question

Could someone show me how one is supposed to implement composition+inheritance in swagger 2.0 (JSON), preferably by "fixing" my example code below. It would also be great if I could specify an ErrorResponse class that inherits from response where the "result" attribute in the header is always set to "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"
                }
            }
        }
    }
}

Diagram Update

To try and clarify what I want, I have created the very basic diagram below which aims to show that all responses are instantiations of the "response" object that have been built by (composition) using any combination of response_header and response_body objects. The response_header and response_body objects can be extended and inserted into any response object, which is done in the case of a filename_response which uses the filename_response_body child of the base response_body class. Both error and successful responses use the "response" object.

enter image description here

Tramroad answered 9/1, 2015 at 14:9 Comment(13)
There is a sample for composition, but it's so bad it's not worth sharing. I'll work on how your spec should look like. Keep in mind, the UI currently does not support it, but it will when full support for 2.0 is available.Enclosure
And before I dive in, one more thing - are you looking for composition or inheritance? Composition is basically saying I have the properties of X and my own properties.. Inheritance suggests a relationship X is my parent. I have its properties and my own.. Inheritance is useful if you want to say that a certain set of models are applicable of the parent is being used.Enclosure
I was rather hoping to demonstrate the use of both inheritance and composition in one go with this example. Obviously I realize that one could easily use either on it's own, but in this case all responses are children of the base "response" class. And the response class is "composed" of two other objects, the header and body.Tramroad
I may have not been clear. Inheritance is an extension of composition. If there's inheritance, there's composition. If there's composition, there's not necessarily inheritance. Also, in your sample, the "response" model is not used anywhere. Should I ignore that and just show how it should look?Enclosure
ah, did not realize that relationship between inheritance and composition. So use inheritance to show both. With regards to response model not being used, it should be used with the "extends" in the filename_response child which the request responds with.Tramroad
Sorry for the delay. I'm trying to work out a sample for you, but get lost by your current definitions (especially since they're not valid). Can you edit the question showing structure samples of the variants you want to support for the response object? It seems a bit different than your previous question regarding the response and I want to make sure I follow your requirements.Enclosure
To be clear, if it's split between a successful and unsuccessful response, just show an example of these two objects (no JSON schema).Enclosure
@Enclosure thanks for all your time, I have added a diagram to try and clarify things. I hope this helps.Tramroad
Looks like you've put a lot of work into it, but it's actually even more confusing. I have a feeling you're trying to give it more hierarchy than needed. Really, a few sample JSONs representing the 2-3 different options would be much easier. Let's start simple and expand if needed.Enclosure
@Enclosure good plan. Could you provide a simple example of inheritance and an example of composition? That was the main point of my question and unfortunately we got lost on my specific implementation of my example. I feel that my question should be rewritten as my example appears to be poor as I tried to mix both composition and inheritance.Tramroad
I'm working on one for the docs. Will post here when it's ready.Enclosure
The spec is updated with the sample. I could provide an answer to the question if needed.Enclosure
@Enclosure Thanks, I've had a brief look and it looks great, especially with the examples.Tramroad
A
133

As a beginner in swagger I don't find the official documentation about polimorphism and composition easy to undestand, because it lacks an example. When I searched the net, there are lots of good examples refering to swagger 1.2 when extends was valid.

For swagger 2.0 I found a good example in swagger spec sources on github via this google group

Based on above sources, here is a short valid inheritance example 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
Alkene answered 10/3, 2016 at 12:31 Comment(6)
Really thanks! This works for me. In editor.swagger.io, I see a little bug: In the models section, I see the Pet model multiple times. The content of these models are ok. Only the names are wrong.Wingspread
@Wingspread In editor2.swagger.io you won't see this problemKronick
The only issue I found with this way of defining the inheritance, is that the petType property is a bit useless in the generated class. It will be empty. But at least it does generate the class hierarchy as I thought it would. Thanks!Tabber
In order to create the inheritance json as above, you have to annotate your parent and children classes as following: @ApiModel(discriminator="type", subTypes ={Cat.class, Dog.class}) public abstract class Animal {} @ApiModel(parent = Animal.class) public calss Cat extends Animal{}Novak
Is the discriminator used only when we implement an Interface Pet, how about if class A extends class B, should we also use it? Thank youRamiah
any way to have inheritance without the discriminator?Borgia
C
26

I've found that composition works fine even without definition of discriminator.

For example, 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

Is rendered as:

Response visualization

And we can extend it to refine custom schema of result field:

  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

And it will be correctly rendered as:

FooServiceResponse visualization

Note, that allOf is enough for this to work and no discriminator field is used. This is good, because it works and this is important, as I think, tools will be able to generate code without discriminator field.

Cd answered 19/12, 2016 at 13:52 Comment(1)
I have also used allOf, but somehow in the openapi.yaml, I find that the subclasses contains the properties of the super class in a redundant way, is that correct?Ramiah
T
10

All the answers here are excellent already, but I just want to add a minor note about composition versus inheritance. According to the Swagger/OpenAPI Spec, to implement composition, using the allOf property is enough, as @oblalex correctly points out. However, to implement inheritance, you need to use allOf with discriminator, as in the example by @TomaszSętkowski.

Also, I found some more Swagger examples of both composition and inheritance at API Handyman. They're part of an excellent Swagger/OpenAPI tutorial series by Arnaud Lauret that I think everyone should check out.

Twister answered 16/2, 2017 at 5:51 Comment(1)
@Though posting relevant links is a good start, to actually be a useful answer, you should also cite the relevant text that will be found at the link. Link-only answers are discouraged, because links will go dead often.Dimitris
D
2

The Swagger 2.0 standard example you have shared depicts a composition relationship, specifically it captures an "is a kind of" super-type/sub-type relationship however it is not polymorphism in and of itself.

It would be if you could reference the base definition of Pet as an input parameter, then pick Cat or enter a Cat JSON object as the value for the input request, and have this acceptable to Swagger UI.

I could not get this to directly work.

The best I could get working was to set additionalProperties to true on the base object (e.g. Pet), specify Pet using JSON pointer reference as the input schema, and finally copy and paste my Cat JSON value object into Swagger UI. Since the additional properties are allowed Swagger UI generated a valid input request payload.

Decalcify answered 10/5, 2016 at 21:31 Comment(2)
You dont do polymorphism over the wire ( or expose your data entities) ..unless you want to tightly couple to the specific hack to make it work .Hotheaded
Polymorphism is enabled by inheritance but is not required to use inheritance. Logically, inheritance is an "is-a" relationship while composition is a "has-a" relationship. The line between the two can be blurry depending on implementation language and domain use cases. But that is the starting point. Fwiw, the discriminator enables deserialization of polymorphic types. There are other approaches (e.g. including Java class names). But, agreed, these can be kludgy and not portable. E.g. what will a python client do with Java class names?Illyria

© 2022 - 2024 — McMap. All rights reserved.