Writing OpenAPI (Swagger) Specification Tutorial Series - Part 3
Simplifying specification file
By Arnaud Lauret, March 19, 2016
After learning the basics and having written a little bit huge file for a so simple API, you may be concerned by what nightmare it could be to handle a bigger and more complex API. REST assured that the OpenAPI Specification (formerly Swagger Specification) format offers all means to write really small and simple specification files whatever the described API’s size and complexity.
Writing OpenAPI (Swagger) Specification Tutorial Series
This tutorial teaches everything about the OpenAPI 2.0 Specification (fka. as Swagger), most of what you’ll read here can still be applied on version 3.
If you’re a bit lost in the specification (version 2 or 3), take a look at the OpenAPI Map:
In this third part you will learn how to simplifiy the specification file by defining reusable definitions, responses and parameters and using them with references and thus make the writing and reading of OpenAPI Specification fairly easy.
Simplifying data model description
We’ll use the final example of the previous part as starting point. When taking a look at this specification file, the obvious problem is that a Person is defined three times:
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
404:
description: The Person does not exists.
By using reusable definitions this not so simple specification will be transformed into this one:
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
$ref: "#/definitions/Persons"
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
$ref: "#/definitions/Person"
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
$ref: "#/definitions/Person"
404:
description: The Person does not exists.
definitions:
Person:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
Persons:
type: array
items:
$ref: "#/definitions/Person"
The OpenAPI Specification definitions section (Swagger Object) allows you to define once and for all objects/entities/models that can be used anywhere in the specification (i.e. where a schema is defined).
Adding definitions section
We’ll start by adding a new definitions section at the end of the document (nb: it can be placed anywhere as long as it’s on the root of the OpenAPI Specification tree structure).
Defining a reusable definition
Then we define a Person once and for all in the definitions section:
definitions:
Person:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
Note that the information provided in Person are exactly the same as the one provided in the three schemas describing a person in previous version. A definition is simply a named schema object.
Referencing a definition from another definition
We’ll start using this newly created definition by referencing it in another one. As we have defined a Person we’ll also define Persons which is an array (or a list) of Persons. The information provided in Persons are almost the same as the one provided in the get /persons response:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
The only difference is that the schema describing the array’s items has been replaced by a reference ($ref) to the Persons definition.
A reference is only a path to another declaration within the OpenAPI Specification.
Using definitions in responses
Once we have defined Person and Persons, we can use them to replace the inline schemas by references in all operations responses.
get /persons
Before
responses:
200:
description: A list of Person
schema:
type: array
items:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
After
get /persons/{username}
Before
responses:
200:
description: A Person
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
After
Using definitions in parameters
Definitions are not only meant to be used within the definitions section (it would be pointless), they can also be used in operations parameters.
post /persons:
Before
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
After
Simplifying responses description
Now we have discovered references ($ref), we’ll see they can be used for other matters like responses definition.
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
$ref: "#/definitions/Persons"
500:
$ref: "#/responses/Standard500ErrorResponse"
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
$ref: "#/definitions/Person"
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
500:
$ref: "#/responses/Standard500ErrorResponse"
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
$ref: "#/definitions/Person"
404:
description: The Person does not exists.
500:
$ref: "#/responses/Standard500ErrorResponse"
definitions:
Person:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
Persons:
type: array
items:
$ref: "#/definitions/Person"
Error:
properties:
code:
type: string
message:
type: string
responses:
Standard500ErrorResponse:
description: An unexpected error occured.
schema:
$ref: "#/definitions/Error"
Defining a reusable HTTP 500 response
Let’s say, we want every API’s operation to return an error code and message to provide more detailed information about what happened when an operation failed with an HTTP 500 error. If we do that the basic way, we’ll end adding a 500 response on each operation like this:
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
$ref: "#/definitions/Persons"
500:
description: An unexpected error occured.
schema:
properties:
code:
type: string
message:
type: string
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
$ref: "#/definitions/Person"
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
500:
description: An unexpected error occured.
schema:
properties:
code:
type: string
message:
type: string
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
$ref: "#/definitions/Person"
404:
description: The Person does not exists.
500:
description: An unexpected error occured.
schema:
properties:
code:
type: string
message:
type: string
As every operation will handle HTTP 500 error exactly the same way, it’s a pity to have to declare three times exactly the same thing.
Defining an Error definition
But, we have learned that we can define a schema once and for all. So let’s create an Error definition containing string code and message in the definitions section.
definitions:
Person:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
Persons:
type: array
items:
$ref: "#/definitions/Person"
Error:
properties:
code:
type: string
message:
type: string
A first almost good idea would be to use this definition in every operation for 500 response schema.
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A list of Person
schema:
$ref: "#/definitions/Persons"
500:
description: An unexpected error occured.
schema:
$ref: "#/definitions/Error"
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
$ref: "#/definitions/Person"
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
500:
description: An unexpected error occured.
schema:
$ref: "#/definitions/Error"
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
$ref: "#/definitions/Person"
404:
description: The Person does not exists.
500:
description: An unexpected error occured.
schema:
$ref: "#/definitions/Error"
Defining a reusable response
But it can be even more simple if we declare a response once and for all within the OpenAPI Specification responses section (Swagger Object). This section allows to define reusable responses definitions.
definitions:
Person:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
Persons:
type: array
items:
$ref: "#/definitions/Person"
Error:
properties:
code:
type: string
message:
type: string
responses:
Standard500ErrorResponse:
description: An unexpected error occured.
schema:
$ref: "#/definitions/Error"
Note that the response use the Error definition.
Using the defined response
Using a defined response is done through a $ref (just like we did when using definitions).
get /users
responses:
200:
description: A list of Person
schema:
$ref: "#/definitions/Persons"
500:
$ref: "#/responses/Standard500ErrorResponse"
post /users
responses:
204:
description: Persons succesfully created.
400:
description: Persons couldn't have been created.
500:
$ref: "#/responses/Standard500ErrorResponse"
get /users/{username}
Simplifying parameters description
Like models/schemas and responses, parameters description can be simplified easily.
swagger: "2.0"
info:
version: 1.0.0
title: Simple API
description: A simple API to learn how to write OpenAPI Specification
schemes:
- https
host: simple.api
basePath: /openapi101
paths:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- $ref: "#/parameters/pageSize"
- $ref: "#/parameters/pageNumber"
responses:
200:
description: A list of Person
schema:
$ref: "#/definitions/Persons"
500:
$ref: "#/responses/Standard500ErrorResponse"
post:
summary: Creates a person
description: Adds a new person to the persons list.
parameters:
- name: person
in: body
description: The person to create.
schema:
$ref: "#/definitions/Person"
responses:
204:
description: Person succesfully created.
400:
description: Person couldn't have been created.
500:
$ref: "#/responses/Standard500ErrorResponse"
/persons/{username}:
parameters:
- $ref: "#/parameters/username"
get:
summary: Gets a person
description: Returns a single person for its username.
responses:
200:
description: A Person
schema:
$ref: "#/definitions/Person"
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
delete:
summary: Deletes a person
description: Delete a single person identified via its username
responses:
204:
description: Person successfully deleted.
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
/persons/{username}/friends:
parameters:
- $ref: "#/parameters/username"
get:
summary: Gets a person's friends
description: Returns a list containing all persons. The list supports paging.
parameters:
- $ref: "#/parameters/pageSize"
- $ref: "#/parameters/pageNumber"
responses:
200:
description: A person's friends list
schema:
$ref: "#/definitions/Persons"
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
definitions:
Person:
required:
- username
properties:
firstName:
type: string
lastName:
type: string
username:
type: string
Persons:
type: array
items:
$ref: "#/definitions/Person"
Error:
required:
- code
- message
properties:
code:
type: string
message:
type: string
responses:
Standard500ErrorResponse:
description: An unexpected error occured.
schema:
$ref: "#/definitions/Error"
PersonDoesNotExistResponse:
description: Person does not exist.
parameters:
username:
name: username
in: path
required: true
description: The person's username
type: string
pageSize:
name: pageSize
in: query
description: Number of persons returned
type: integer
pageNumber:
name: pageNumber
in: query
description: Page number
type: integer
Defining a path parameter once for a path
If we add a delete operation to the /persons/{username} path, a first idea could be to do it that way:
/persons/{username}:
get:
summary: Gets a person
description: Returns a single person for its username.
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
200:
description: A Person
schema:
$ref: "#/definitions/Person"
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
delete:
summary: Deletes a person
description: Delete a single person identified via its username
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
responses:
204:
description: Person successfully deleted.
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
Note that we had the good idea to create a reusable 404 response.
But having to redefine the username parameter which is shared by each operation on /persons/{username} path is a bit cumbersome. Luckily you can define parameters on path level:
/persons/{username}:
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
get:
summary: Gets a person
description: Returns a single person for its username.
responses:
200:
description: A Person
schema:
$ref: "#/definitions/Person"
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
delete:
summary: Deletes a person
description: Delete a single person identified via its username
responses:
204:
description: Person successfully deleted.
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
Defining reusable parameters
If we add a get /persons/{username}/friends operations to get a paginated list of a person’s friends, a first idea could be to do it that way:
/persons/{username}/friends:
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
get:
summary: Gets a person's friends
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
responses:
200:
description: A person's friends list
schema:
$ref: "#/definitions/Persons"
404:
$ref: "#/responses/PersonDoesNotExistResponse"
500:
$ref: "#/responses/Standard500ErrorResponse"
Note that we reuse the 500 and 404 responses.
But that way, we redefine the username parameter just like on path /persons/{username}:
/persons/{username}:
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
And we also redefine the pageNumber and pageSize parameters which already exist on get /persons:
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
Define reusable parameters
As we have done with definitions and responses, we can define reusable parameters within the OpenAPI Specification parameters section (Swagger Object).
parameters:
username:
name: username
in: path
required: true
description: The person's username
type: string
pageSize:
name: pageSize
in: query
description: Number of persons returned
type: integer
pageNumber:
name: pageNumber
in: query
description: Page number
type: integer
Use reusable parameters
These reusable parameters can be used with a reference just like definitions and responses.
get /persons
Before
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
After
/persons:
get:
summary: Gets some persons
description: Returns a list containing all persons. The list supports paging.
parameters:
- $ref: "#/parameters/pageSize"
- $ref: "#/parameters/pageNumber"
get and delete /persons/{username}
Before
/persons/{username}:
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
After
get /persons/{username}/friends
Before
/persons/{username}/friends:
parameters:
- name: username
in: path
required: true
description: The person's username
type: string
get:
summary: Gets a person's friends
description: Returns a list containing all persons. The list supports paging.
parameters:
- name: pageSize
in: query
description: Number of persons returned
type: integer
- name: pageNumber
in: query
description: Page number
type: integer
After
Conclusion
By defining reusable definitions, responses and parameters and using them with references you can easily write OpenAPI Specification files which are simple and easily understandable. In next post, we’ll dig deeper in various aspect of the OpenAPI Specification.