Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swagger Inheritance and Composition

Tags:

swagger

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

like image 923
Programster Avatar asked Jan 09 '15 14:01

Programster


People also ask

What is anyOf in Swagger?

Use the anyOf keyword to validate the data against any amount of the given subschemas. That is, the data may be valid against one or more subschemas at the same time.

What are the three primary sections in a Swagger specification?

Basic authentication. API key (as a header or query parameter) OAuth 2 common flows (implicit, password, application and access code)

What is schema in Swagger?

OpenAPI 3.0 data types are based on an extended subset JSON Schema Specification Wright Draft 00 (aka Draft 5). The data types are described using a Schema object. To learn how to model various data types, see the following topics: Data Types.

What is discriminator in Swagger?

The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. The property name used MUST be defined at this schema and it MUST be in the required property list. When used, the value MUST be the name of this schema or any schema that inherits it.


2 Answers

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 
like image 101
Tomasz Sętkowski Avatar answered Oct 05 '22 22:10

Tomasz Sętkowski


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.

like image 25
oblalex Avatar answered Oct 05 '22 21:10

oblalex