With previous posts we have learned to produce an OpenAPI specification containing all OpenAPI specification subtleties. Some specification files may become quite large or may contain elements which could be reused in other APIs. Splitting a specification file will help to keep it maintainable by creating smaller files and also help to ensure consistency throughout APIs by sharing common elements.

Writing OpenAPI (fka Swagger) Specification tutorial

This tutorial is composed of several posts:

If you’re a bit lost in the specification, take a look at my visual documentation:

In previous parts we’ve learned to create highly accurate API description which can become quite large or may contain elements that can be reused, in this eighth part we’ll learn how to split an OpenAPI specification file into smaller and reusable elements.

JSON Pointers

In part 3 - Simplifying spefication file we have learned how to simplify the specification by creating reusable elements. In the example below, the Person definition is defined once in definitions and used as

  • A body parameter in POST /persons
  • A response schema in GET /persons/{username}
  • A sub-elements in Persons 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"
    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"

To use the Person definition in these different places, we use a JSON Pointer (defined by RFC6901):

      $ref: "#/definitions/Person"

This pointer describes a path in the document, pointing to Person in definitions which is as the root (#) of the current document.

But JSON pointers are not only meant to point something within the current document, they can be used to reference something in another document.

Basic splitting

Let’s see how we can split the file we created in part 3

Referencing a local file

We can create a person.yaml file containing the Person definition:

Person:
  required:
    - username
  properties:
    firstName:
      type: string
    lastName:
      type: string
    username:
      type: string

Then we can remove the Person definition in definitions and replace all existing references to person #definitions/Person by a reference to Person in the person.yaml file:

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: "person.yaml#/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: "person.yaml#/Person"
        404:
          description: The Person does not exists.

definitions:
  Persons:
    type: array
    items:
      $ref: "person.yaml#/Person"

Editing splitted local files with the online editor

Tools will look for the referenced file (person.yaml) in the same directory as the file containing the reference. But when you use the online editor it does not make sense, such local reference cannot be resolved.

Fortunately the editor propose a configuration allowing to set a server to resolve these local references. This configuration is accessible in the Preferences->Preferences menu:

The Pointer Resolution Base Path configuration is on the bottom of the configuration screen:

All you need to do is put the referenced yaml files into a web server (with CORS activated) and modify the editor’s configuration to point this server.

http-server a lightweight web server

You can use http-server a lightweight node.js web server:

npm install --global http-server

You now can start a web service on any folder:

http-server --cors path/to/yaml/folder

or

cd path/to/yaml/folder
http-server --cors .

By default, http-server listens on 8080 port, files will be accessble through http://localhost:8080/<path to file within folder>.

The --cors flag is used to activate CORS directive and allow XHR request to this local webserver from the online editor page. Without CORS activated, the editor will not be able to download files.

Modifying online editor configuration

Once the web server is started you can modifiy the editor to set the URL to http://localhost:8080/:

Files referenced with $ref: <filename>#/example will be downloaded from http://localhost:8080/<filename>.

Once this is done, you may need to do a force refresh (clean cache) of the editor’s the page to get the green bar.

Cache

When you add a reference to a new file (that was not already reference), this new file may not be downloaded automatically resulting in errors (Reference could not be resolved: newfile.yaml). You may need to refresh the editor’s page with cache cleaning to solve this error.

Folders

You’re under no obligation to put all sub-files on a “root” level, you can store them in differents sub-folders.

Reference to a file in a folder

Let’s move the person.yaml file into a sub-folder folder, the new reference will be like this:

            $ref: "folder/person.yaml#/Person"

File referencing a file from an upper folder

You can also reference a file outside the current folder. Let’s create a persons.yaml file into a another-folder folder:

Persons:
  type: array
  items:
    $ref: "../folder/person.yaml#/Person"

This file reference the person.yaml file using .. to get to the upper level.

To use persons.yaml, we proceed just like with person.yaml. We remove the Persons definition from the main file and we replace its reference #/definitions/Persons by another-folder/persons.yaml#Persons.

Here’s the full file with all definitions externalized (the definitions section has been removed):

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: "another-folder/persons.yaml#/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: "folder/person.yaml#/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: "folder/person.yaml#/Person"
        404:
          description: The Person does not exists.

Referencing a remote files

As you may have guess while modifying the references in the specification and editor configuration, it is also possible to reference a remote file.

Remote files

All we need to do is to put the full file’s URL in the reference:

$ref: https://myserver.com/mypath/myfile.yaml#/example

Remember that the server MUST have CORS activated to allow the editor to download the file.

We have launched a web server on 8080 port, so all we have to do is add http://localhost:8080/ to the references to Person:

            $ref: "http://localhost:8080/folder/person.yaml#/Person"

Remote files containing local references

What happen if the remote file reference a local file? (Main file -> Remote file -> Local file).

The parser will seek this “local” file on the remote server providing the remote file.

If we replace the local to Persons by a remote reference…

            $ref: "http://localhost:8080/another-folder/persons.yaml#/Persons"

… the person.yaml file referenced “locally” in the persons.yaml file will be loaded from http://localhost:8080 which has served the persons.yaml.

Remote files containing remote references

If a remote file contains a remote reference (Main file -> Remote file -> Remote file ), it will be resolved like in 2.4.1 Remote files.

We can replace the person.yaml file local reference by a remote reference in persons.yaml:

Persons:
  type: array
  items:
    $ref: "http://localhost:8080/folder/person.yaml#/Person"

Multiple items in a single sub-file

We have put Person and Persons definitions in separate files: this is not an obligation. You can put more than one item in a single file, you only need to use the right JSON Pointer.

Person and Person in a single file

If we concatenate person.yaml and persons.yaml into a single file called definitions.yaml:

Person:
  required:
    - username
  properties:
    firstName:
      type: string
    lastName:
      type: string
    username:
      type: string

Persons:
  type: array
  items:
    $ref: "#/Person"

Note that in Persons the reference to Person is now #/Person.

In the main file we only need to change the filename when reference these two definitions:

            $ref: "definitions.yaml#/Persons"
            $ref: "definitions.yaml#/Person"

Organizing content in a sub-file

Within the sub-file you can organize the content as you wish. Here Person is in SomeDefinitions and Persons is in OtherDefinitions:

SomeDefinitions:
  Person:
    required:
      - username
    properties:
      firstName:
        type: string
      lastName:
        type: string
      username:
        type: string

OtherDefinitions:
  Persons:
    type: array
    items:
      $ref: "#/SomeDefinitions/Person"

Note that in Persons the reference to Person is now #/SomeDefinitions/Person.

In the main file we have to modify the path to get the item in the sub-file for these two definitions:

            $ref: "definitions.yaml#/OtherDefinitions/Persons"
            $ref: "definitions.yaml#/SomeDefinitions/Person"

Definitions, Responses, Parameters

What we have done with Person and Persons definitions can be done with any reusable items (i.e. using $ref) such as responses and parameters in the Open API Specification file.

OpenAPI chainsaw massacre

Thanks to Mohsen Azimi’s post I’ve discovered that using sub-files for definitions, responses and parameters which obviously use $ref JSON Pointers is not the only way of using sub-files. You can use sub-files for almost anything in the specification.

We will split the huge file created in previous post about documentation.

Let the chainsaw massacre begin

Let’s start with the info section:

swagger: '2.0'

info:
  version: 1.1.0
  title: Simple API
  description: |
    A simple API to learn how to write OpenAPI Specification.
    This file uses almost every single aspect of the [Open API Specification](https://openapis.org/).  
    This API will use JSON.  
    JSON looks like this:  
    
    ```JSON
    {
      "key": "value",
      "anotherKey": "anotherValue"
    }
    ```
  termsOfService: http://simple.api/terms-of-service
  contact:
    name: John Doe
    url: http://simple.api/contact
    email: [email protected]
  license:
    name: Apache-2.0
    url: http://www.apache.org/licenses/LICENSE-2.0

We can put its whole content in a file called info.yaml:

version: 1.1.0
title: Simple API
description: |
  A simple API to learn how to write OpenAPI Specification.
  This file uses almost every single aspect of the [Open API Specification](https://openapis.org/).  
  This API will use JSON.  
  JSON looks like this:  
  
  ```JSON
  {
    "key": "value",
    "anotherKey": "anotherValue"
  }
  ```
termsOfService: http://simple.api/terms-of-service
contact:
  name: John Doe
  url: http://simple.api/contact
  email: [email protected]
license:
  name: Apache-2.0
  url: http://www.apache.org/licenses/LICENSE-2.0

And reference it just like this in info:

swagger: '2.0'

info:
  $ref: info.yaml

The wizard of resolution

We can do the same thing with paths, definitions, responses and parameters: copy the section content in a sub-file (paths.yaml, definitions.yaml, responses.yaml and parameters.yaml) and reference it in the main file:

paths:
  $ref: paths.yaml

definitions:
  $ref: definitions.yaml
  
responses:
  $ref: responses.yaml

parameters:
  $ref: parameters.yaml

If you remember previous posts, these sections references each other in many ways, for example in paths.yaml:

'/persons/{username}':
  parameters:
    - $ref: '#/parameters/username'

The username parameter is no longer in the main file and is not in the paths.yaml file but in the parameters.yaml file:

username:
  name: username
  in: path
  required: true
  description: The person's username
  type: string

How could the main file be considered valid in the editor (or in any tool parsing the file)? It’s simply because the sub-files are loaded and then the whole content (file + sub-files) is validated.

A less rough split

We can use JSON pointers for almost anything in the specification as as long as the $ref JSON pointer reference something corresponding to the expected object (or value) in the OpenAPI specification.

Referencing object in custom structure

And as seen earlier in this post we can put many items in a sub-file.

The documentation.yaml file contains the data for externalDocs and tags (note the custom structure on line 1 and 6):

external:
  description: |
    **Complete** documentation describing how to use this API
  url: http://doc.simple.api/

categories:
  - name: Persons
    description: Everything you need to handle `users` and `friends`
    externalDocs:
      description: People category documentation
      url: http://doc.simple.api/people
  - name: Items
    description: Everything you need to handle items collected by users
    externalDocs:
      description: Items category documentation
      url: http://doc.simple.api/items
  - name: Media
    description: Everything you need to handle images
    externalDocs:
      description: Media category documentation
      url: http://doc.simple.api/media
  - name: JSLess
    description: Specific operations for JS less consumers
    externalDocs:
      description: JS Less Consumers documentation
      url: http://doc.simple.api/jsless

These data are referenced this way in the main file:

externalDocs: 
  $ref: documentation.yaml#/external

tags:
  $ref: documentation.yaml#/categories

The security.yaml file contains the data for securityDefinitions and security (note the custom structure on line 20):

securityDefinitions:
  OauthSecurity:
    description: New Oauth security system. Do not use MediaSecurity or LegacySecurity.
    type: oauth2
    flow: accessCode
    authorizationUrl: 'https://oauth.simple.api/authorization'
    tokenUrl: 'https://oauth.simple.api/token'
    scopes:
      admin: Admin scope
      user: User scope
  MediaSecurity:
    description: Specific media security for backward compatibility. Use OauthSecurity instead.
    type: apiKey
    in: query
    name: media-api-key
  LegacySecurity:
    description: Legacy security system for backward compatibility. Use OauthSecurity instead.
    type: basic

defaultSecurity:
  - OauthSecurity:
    - user
  - LegacySecurity: []

Referencing a string or a simple list

It also work for simpler value like a string or a list of string. The schema, host and basepath values can be moved into a single file security.yaml (note the custom structure on line 3 and 4):

schemes:
  - https
server_and_port: simple.api
path: /openapi102

And referenced like this

schemes:
  $ref: endpoint.yaml#/schemes
host:
  $ref: endpoint.yaml#/server_and_port
basePath:
  $ref: endpoint.yaml#/path

Reusing a value

As long as the API consumes and produces the same media types, we define a single value in the mediatypes.yaml file:

default:
  - application/json
  - application/x-yaml

And then we reference this single value in both produces and consumes:

consumes:
  $ref: mediatypes.yaml#/default
produces:
  $ref: mediatypes.yaml#/default

A smarter split

The API we built with the previous parts could be divided in four parts:

  • Common items like headers, media types, security, definitions, parameters and responses
  • Persons operations, parameters, responses and definitions
  • Legacy operations
  • Images operations

We can create 4 sub-files and reference them from a main file.

commons.yaml

In the commons.yaml file we put every items that can be reused across other files:

securityDefinitions:
  OauthSecurity:
    description: New Oauth security system. Do not use MediaSecurity or LegacySecurity.
    type: oauth2
    flow: accessCode
    authorizationUrl: 'https://oauth.simple.api/authorization'
    tokenUrl: 'https://oauth.simple.api/token'
    scopes:
      admin: Admin scope
      user: User scope
  MediaSecurity:
    description: Specific media security for backward compatibility. Use OauthSecurity instead.
    type: apiKey
    in: query
    name: media-api-key
  LegacySecurity:
    description: Legacy security system for backward compatibility. Use OauthSecurity instead.
    type: basic

defaultSecurity:
  - OauthSecurity:
    - user
  - LegacySecurity: []

defaultMediatypes:
  - application/json
  - application/x-yaml

defaultHeaders:
  X-Rate-Limit-Remaining:
    description: How many calls consumer can do
    type: integer
  X-Rate-Limit-Reset:
    description: When rate limit will be reset
    type: string
    format: date-time

parameters:
  userAgent:
    name: User-Agent
    description: All API consumers MUST provide a user agent
    type: string
    in: header
    required: true
  pageSize:
    name: pageSize
    in: query
    description: Number of items returned
    type: integer
    format: int32
    minimum: 0
    exclusiveMinimum: true
    maximum: 100
    exclusiveMaximum: false
    multipleOf: 10
    default: 20
  pageNumber:
    name: pageNumber
    in: query
    description: Page number
    type: integer
    default: 1

responses:
  Standard500ErrorResponse:
    description: An unexpected error occured.
    headers:
      $ref: '#/defaultHeaders'
    schema:
      $ref: '#/definitions/Error'
  TotallyUnexpectedResponse:
    description: A totally unexpected response
    headers:
      $ref: '#/defaultHeaders'

definitions:
  ErrorMessage:
    title: MultiDeviceErrorMessage
    description: An error message with a long and a short description
    required:
      - longMessage
      - shortMessage
    properties:
      longMessage:
        description: A long error description
        type: string
      shortMessage:
        description: A short error description
        type: string
  MultilingualErrorMessage:
    title: MultiLingualMultiDeviceErrorMessage
    description: An multilingual error message (hashmap) with a long and a short description
    additionalProperties:
      $ref: '#/definitions/ErrorMessage'
    properties:
      defaultLanguage:
        $ref: '#/definitions/ErrorMessage'
    example:
      defaultLanguage:
        longMessage: We're deeply sorry but an error occured
        shortMessage: Error
      fr:
        longMessage: Nous sommes désolé mais une erreur est survenu
        shortMessage: Erreur
        
  Error:
    title: MultiLingualMultiDeviceError
    description: Give full information about the problem
    required:
      - code
      - message
    properties:
      code:
        description: A human readable code (death to numeric error codes!)
        type: string
        enum:
          - DBERR
          - NTERR
          - UNERR
        example: UNERR
      message:
        $ref: '#/definitions/MultilingualErrorMessage'
  Paging:
    required:
      - totalItems
      - totalPages
      - pageSize
      - currentPage
    properties:
      totalItems:
        type: integer
      totalPages:
        type: integer
      pageSize:
        type: integer
      currentPage:
        type: integer

images.yaml

In the images.yaml file we put both /images and /images/{id} paths informations:

  images:
    parameters:
      - $ref: 'commons.yaml#/parameters/userAgent'
    post:
      summary: Uploads an image
      description: Upload an image, will return an image id. 
      operationId: storeImage
      externalDocs:
        description: How to upload media
        url: http://doc.simple.api/media/upload
      tags:
        - Media
      security:
        - MediaSecurity: []
      consumes:
        - multipart/form-data
      parameters:
        - name: image
          in: formData
          type: file
      responses:
        '200':
          description: Image's ID
          schema:
            properties:
              imageId:
                type: string
          headers:
            $ref: commons.yaml#/defaultHeaders
        '500':
          $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
        default:
          $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'
  images-imageId:
    parameters:
      - $ref: 'commons.yaml#/parameters/userAgent'
    get:
      summary: Gets an image
      description: Return an image 
      operationId: readImage
      tags:
        - Media
      parameters:
        - name: imageId
          in: path
          required: true
          type: string
      produces:
        - image/png
        - image/gif
        - image/jpeg
        - application/json
        - application/x-yaml
      responses:
        '200':
          description: The image
          headers:
            $ref: commons.yaml#/defaultHeaders
        '404':
          description: Image do not exists
          headers:
            $ref: commons.yaml#/defaultHeaders
        '500':
          $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
        default:
          $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'

Note that all references to common items point to commons.yaml.

legacy.yaml

Same for the /js-less-consumer-persons path we put in legacy.yaml file in js-less-consumer-persons:

js-less-consumer-persons:
  parameters:
    - $ref: 'commons.yaml#/parameters/userAgent'
  post:
    summary: Creates a person
    description: For JS-less partners
    operationId: createUserJS
    deprecated: true
    tags:
      - JSLess
      - Persons
    security:
      - OauthSecurity:
        - admin
      - LegacySecurity: []
    consumes:
      - application/x-www-form-urlencoded
    produces:
      - text/html
    parameters:
      - name: username
        in: formData
        required: true
        pattern: '[a-z0-9]{8,64}'
        minLength: 8
        maxLength: 64
        type: string
      - name: firstname
        in: formData
        type: string
      - name: lastname
        in: formData
        type: string
      - name: dateOfBirth
        in: formData
        type: string
        format: date
    responses:
      '204':
        description: Person succesfully created.
        headers:
          $ref: 'commons.yaml#/defaultHeaders'
      '400':
        description: Person couldn't have been created.
        headers:
          $ref: 'commons.yaml#/defaultHeaders'
      '500':
        description: An error occured.
        headers:
          $ref: 'commons.yaml#/defaultHeaders'
      default:
        $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'

persons.yaml

All persons items go in the persons.yaml:

  • each path go in its own named section (like persons-username for /persons/{username})
  • all persons specific definitions goes in definitions (and can be referenced with #definition/name)
  • all persons specific responses goes in responses (and can be referenced with #responses/name)
  • all persons specific parameters goes in parameters (and can be referenced with #parameters/name)
persons:
  parameters:
    - $ref: 'commons.yaml#/parameters/userAgent'
  get:
    summary: Gets some persons
    description: Returns a list containing all persons. The list supports paging.
    operationId: searchUsers
    tags:
      - Persons
    parameters:
      - $ref: 'commons.yaml#/parameters/pageSize'
      - $ref: 'commons.yaml#/parameters/pageNumber'
      - $ref: '#/parameters/includeNonVerifiedUsers'
      - $ref: '#/parameters/sortPersons'
    responses:
      '200':
        description: A list of Person
        schema:
          $ref: '#/definitions/Persons'
        headers:
          $ref: commons.yaml#/defaultHeaders
      '500':
        $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
      default:
        $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'
  post:
    summary: Creates a person
    description: Adds a new person to the persons list.
    operationId: createUser
    tags:
      - Persons
    security:
      - OauthSecurity:
        - admin
      - LegacySecurity: []
    parameters:
      - name: person
        in: body
        required: true
        description: The person to create.
        schema:
          $ref: '#/definitions/Person'
    responses:
      '204':
        description: Person succesfully created.
        headers:
          $ref: commons.yaml#/defaultHeaders
      '400':
        description: Person couldn't have been created.
        headers:
          $ref: commons.yaml#/defaultHeaders
      '500':
        $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
      default:
        $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'

persons-username:
  parameters:
    - $ref: '#/parameters/username'
    - $ref: 'commons.yaml#/parameters/userAgent'
  get:
    summary: Gets a person
    description: Returns a single person for its username.
    operationId: readPerson
    tags:
      - Persons
    responses:
      '200':
        description: A Person
        schema:
          $ref: '#/definitions/Person'
        headers:
          $ref: commons.yaml#/defaultHeaders
      '404':
        $ref: '#/responses/PersonDoesNotExistResponse'
      '500':
        $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
      default:
        $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'
  delete:
    summary: Deletes a person
    description: Delete a single person identified via its username
    operationId: deletePerson
    tags:
      - Persons
    responses:
      '204':
        description: Person successfully deleted.
        headers:
          $ref: commons.yaml#/defaultHeaders
      '404':
        $ref: '#/responses/PersonDoesNotExistResponse'
      '500':
        $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
      default:
        $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'

persons-username-friends:
  parameters:
    - $ref: '#/parameters/username'
    - $ref: 'commons.yaml#/parameters/userAgent'
  get:
    summary: Gets a person's friends
    description: Returns a list containing all persons. The list supports paging.
    operationId: readPersonsFriends
    tags:
      - Persons
    parameters:
      - $ref: 'commons.yaml#/parameters/pageSize'
      - $ref: 'commons.yaml#/parameters/pageNumber'
      - $ref: '#/parameters/includeNonVerifiedUsers'
      - $ref: '#/parameters/sortPersons'
    responses:
      '200':
        description: A person's friends list
        schema:
          $ref: '#/definitions/PagedPersons'
        headers:
          $ref: commons.yaml#/defaultHeaders
      '404':
        $ref: '#/responses/PersonDoesNotExistResponse'
      '500':
        $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
      default:
        $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'

persons-username-collecting-items:
  parameters:
    - $ref: '#/parameters/username'
    - $ref: 'commons.yaml#/parameters/userAgent'
  get:
    summary: Gets a person's collecting items list
    description: |
      Returns a list containing all items this person is looking for.  
      The list supports paging.
    operationId: readPersonsCollectingItems
    tags:
      - Items
    parameters:
      - $ref: 'commons.yaml#/parameters/pageSize'
      - $ref: 'commons.yaml#/parameters/pageNumber'
      - $ref: '#/parameters/filterItemTypes'
    responses:
      '200':
        description: A collected items list
        schema:
          $ref: '#/definitions/PagedCollectingItems'
        headers:
          $ref: commons.yaml#/defaultHeaders
        examples:
          application/json:
            {
              "totalItems": 10,
              "totalPage": 4,
              "pageSize": 3,
              "currentPage": 2,
              "items": 
              [
                { 
                  "itemType": "Vinyl",
                  "maxPrice": 20,
                  "imageId": "98096838-04eb-4bac-b32e-cd5b7196de71",
                  "albumName": "Captain Future Original Soundtrack",
                  "artist": "Yuji Ohno"
                },
                { 
                  "itemType": "VHS",
                  "maxPrice": 10,
                  "imageId": "b74469bc-e6a1-4a90-858a-88ef94079356",
                  "movieTitle": "Star Crash",
                  "director": "Luigi Cozzi"
                },
                { 
                  "itemType": "AudioCassette",
                  "maxPrice": 10,
                  "imageId": "b74469bc-e6a1-4a90-858a-88ef94079356",
                  "albumName": "Star Wars",
                  "artist": "John Williams"
                }
              ]
            }
      '404':
        $ref: '#/responses/PersonDoesNotExistResponse'
      '500':
        $ref: 'commons.yaml#/responses/Standard500ErrorResponse'
      default:
        $ref: 'commons.yaml#/responses/TotallyUnexpectedResponse'

definitions:
  Person:
    title: Human
    description: A person which can be the user itself or one of his friend
    required:
      - username
    properties:
      firstName:
        description: first name
        type: string
        example: John
      lastName:
        description: last name
        type: string
        example: Doe
      username:
        description: Username used to connect to the service
        type: string
        pattern: '[a-z0-9]{8,64}'
        minLength: 8
        maxLength: 64
        example: john1doe6
      dateOfBirth:
        description: Date of birth
        type: string
        format: date
        example: 1978-06-21
      lastTimeOnline:
        description: The last time this person was connected to the service as a 
        type: string
        format: date-time
        readOnly: true
        example: 2016-06-10T12:36:58.014Z
      avatarBase64PNG:
        description: An avatar PNG image as a base64 encoded string ready to use as an src in img html tag
        type: string
        format: byte
        default: data:image/png;base64,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
      spokenLanguages:
        $ref: '#/definitions/SpokenLanguages'
  SpokenLanguages:
    title: Languages
    description: A hashmap of spoken languages
    additionalProperties:
      description: An additional spoken language
      type: string
    properties:
      defaultLanguage:
        description: Default spoken language
        type: string
        default: english
    example:
      defaultLanguage: french
      it: italian
      fr: french
  Persons:
    title: Humans
    description: A list of users or friends
    required:
      - items
    properties:
      items:
        description: Array containg the list
        type: array
        minItems: 10
        maxItems: 100
        uniqueItems: true
        items:
          $ref: '#/definitions/Person'
        example:
          - firstname: Robert
            lastname": Doe
            username": robdo
            dateOfBirth: 1970-01-28
            lastTimeOnline: 2016-04-10T14:36:58.014Z
          - firstname: Jane
            lastname: Doe
            username: jdoe123
            dateOfBirth: 1980-05-12
            lastTimeOnline: 2016-05-12T19:23:59.014Z

  CollectingItem:
    discriminator: itemType
    required:
      - itemType
    properties:
      itemType:
        description: |
          An item can be of different type:
            
          type | definition
          -----|-----------
          Vinyl| #/definitions/Vinyl
          VHS  | #/definitions/VHS
          AudioCassette | #/definitions/AudioCassette
        type: string
        enum:
          - AudioCassette
          - Vinyl
          - VHS
      imageId:
        type: string
      maxPrice:
        type: number
        format: double
        minimum: 0
        maximum: 10000
        exclusiveMinimum: true
        exclusiveMaximum: false
  Vinyl:
    allOf:
      - $ref: '#/definitions/CollectingItem'
      - required:
          - albumName
          - artist
        properties:
          albumName:
            type: string
          artist:
            type: string
  VHS:
    allOf:
      - $ref: '#/definitions/CollectingItem'
      - required:
          - movieTitle
        properties:
          movieTitle:
            type: string
          director:
            type: string
  AudioCassette:
    allOf:
      - $ref: '#/definitions/CollectingItem'
      - required:
          - albumName
          - artist
        properties:
          albumName:
            type: string
          artist:
            type: string

  PagedPersons:
    allOf:
      - $ref: '#/definitions/Persons'
      - $ref: 'commons.yaml#/definitions/Paging'
  PagedCollectingItems:
    allOf:
      - properties:
          items:
            type: array
            minItems: 10
            maxItems: 100
            uniqueItems: true
            items:
              $ref: '#/definitions/CollectingItem'
      - $ref: 'commons.yaml#/definitions/Paging'

responses:
  PersonDoesNotExistResponse:
    description: Person does not exist.
    headers:
      $ref: commons.yaml#/defaultHeaders

parameters:
  username:
    name: username
    in: path
    required: true
    description: The person's username
    type: string
  includeNonVerifiedUsers:
    name: includeNonVerifiedUsers
    in: query
    description: Result will not include non verified user by default if this parameter is not provided
    type: boolean
    default: false
    allowEmptyValue: true
  sortPersons:
    name: sort
    in: query
    description: Result will be sorted by lastTimeOnline descending and username ascending by default if this parameter is not provided
    type: array
    uniqueItems: true
    minItems: 1
    maxItems: 3
    collectionFormat: pipes
    items:
      type: string
      pattern: '[-+](username|lastTimeOnline|firstname|lastname)'
    default:
      - -lastTimeOnline
      - +username
  filterItemTypes:
    name: itemType
    in: query
    description: Filter collected items on their type
    type: array
    collectionFormat: multi
    uniqueItems: true
    items:
      type: string
      enum:
        - AudioCassette
        - Vinyl
        - VHS

full main file

Here’s the full main file where we reference the 4 sub-files:

swagger: '2.0'

info:
  version: 1.1.0
  title: Simple API
  description: |
    A simple API to learn how to write OpenAPI Specification.
    This file uses almost every single aspect of the [Open API Specification](https://openapis.org/).  
    This API will use JSON.  
    JSON looks like this:  
    
    ```JSON
    {
      "key": "value",
      "anotherKey": "anotherValue"
    }
    ```
  termsOfService: http://simple.api/terms-of-service
  contact:
    name: John Doe
    url: http://simple.api/contact
    email: [email protected]
  license:
    name: Apache-2.0
    url: http://www.apache.org/licenses/LICENSE-2.0

externalDocs: 
  description: |
    **Complete** documentation describing how to use this API
  url: http://doc.simple.api/

tags:
  - name: Persons
    description: Everything you need to handle `users` and `friends`
    externalDocs:
      description: People category documentation
      url: http://doc.simple.api/people
  - name: Items
    description: Everything you need to handle items collected by users
    externalDocs:
      description: Items category documentation
      url: http://doc.simple.api/items
  - name: Media
    description: Everything you need to handle images
    externalDocs:
      description: Media category documentation
      url: http://doc.simple.api/media
  - name: JSLess
    description: Specific operations for JS less consumers
    externalDocs:
      description: JS Less Consumers documentation
      url: http://doc.simple.api/jsless
      
schemes:
  - https
host: simple.api
basePath: /openapi101

consumes:
  $ref: commons.yaml#/defaultMediatypes
produces:
  $ref: commons.yaml#/defaultMediatypes

securityDefinitions:
  $ref: commons.yaml#/securityDefinitions

security:
  $ref: commons.yaml#/defaultSecurity

paths:
  /persons:
    $ref: 'persons.yaml#/persons'
  /js-less-consumer-persons:
    $ref: 'legacy.yaml#/js-less-consumer-persons'
  '/persons/{username}':
    $ref: 'persons.yaml#/persons-username'
  '/persons/{username}/friends':
    $ref: 'persons.yaml#/persons-username-friends'
  '/persons/{username}/collecting-items':
    $ref: 'persons.yaml#/persons-username-collecting-items'
  /images:
    $ref: 'images.yaml#/images'
  /images/{imageId}:
    $ref: 'images.yaml#/images-imageId'

Valid sub-files

If we put the last persons.yaml file content in the editor, it ends with these errors:

Splitting a huge Open API Specification file to keep it maintainable is a great idea but if you cannot validate sub-files against the specification, you gain almost nothing.

Making commons.yaml valid

If we put the commons.yaml file created earlier in the editor we get these errors:

  • Missing required property: swagger
  • Missing required property: info
  • Missing required property: paths
  • Additional properties not allowed: defaultHeaders,defaultMediatypes,defaultSecurity

Let’s see how we can fix these errors.

Missing swagger property

We just need to add this line on file’s top:

swagger: "2.0"

Missing info property

We add an short info section after swagger:

info:
  version: 1.0.0
  title: Common elements
  description: Shared elements in all API

Missing paths property

As we do not define any path in this file we just need to add an empty paths section:

paths: {}

Property defaultSecurity not allowed

As the defaultSecurity correspond to the OpenAPI security section we just need to rename it:

Invalid commons.yaml file:

defaultSecurity:
  - OauthSecurity:
    - user
  - LegacySecurity: []

Valid commons.yaml file:

security:
  - OauthSecurity:
    - user
  - LegacySecurity: []

Property defaultMediatypes not allowed

defaultMediaType is not a valid OpenAPI property:

defaultMediatypes:
  - application/json
  - application/x-yaml

We will use produces and consumes to define the default media types. But as we want to define a single set of media type we will use this trick:

produces:
  - application/json
  - application/x-yaml
consumes:
  $ref: '#/produces'

In the future if we want to define different sets of media types for produces and consumes, we’ll be ready.

Property defaultHeaders not allowed

defaultHeaders is not a valid OpenAPI property:

defaultHeaders:
  X-Rate-Limit-Remaining:
    description: How many calls consumer can do
    type: integer
  X-Rate-Limit-Reset:
    description: When rate limit will be reset
    type: string
    format: date-time

We will use a dummy response definition DefaultHeaders to declare these default headers:

responses:
  DefaultHeaders:
    description: A dummy response to define default header
    headers:
      X-Rate-Limit-Remaining:
        description: How many calls consumer can do
        type: integer
      X-Rate-Limit-Reset:
        description: When rate limit will be reset
        type: string
        format: date-time

Of course we need to update common responses to use these headers:

  Standard500ErrorResponse:
    description: An unexpected error occured.
    headers:
      $ref: '#/responses/DefaultHeaders/headers'
    schema:
      $ref: '#/definitions/Error'
  TotallyUnexpectedResponse:
    description: A totally unexpected response
    headers:
      $ref: '#/responses/DefaultHeaders/headers'

Making persons.yaml valid

Just like with commons.yaml, if we put persons.yaml content in the editor we get some errors:

  • Missing required property: swagger
  • Missing required property: info
  • Reference could not be resolved: commons.yaml#/defaultHeaders
  • Missing required property: paths
  • Additional properties not allowed: persons-username-collecting-items,persons-username-friends,persons-username,persons

Missing swagger and info properties

We just add swagger and info like for commons.yaml:

swagger: "2.0"
  
info:
  version: 1.0.0
  title: Persons Sub-API
  description: Persons operations, definitions, parameters and responses

securityDefinitions:
  $ref: commons.yaml#/securityDefinitions

commons.yaml#/defaultHeaders reference could not be resolved

As the commons.yaml structure has been modified concerning default headers we need to replace these references:

          $ref: commons.yaml#/defaultHeaders

by this one:

              $ref: 'commons.yaml#/responses/DefaultHeaders/headers'

Missing paths and additional properties not allowed

We have defined custom properties for each path relative to persons operations, therefore there is no paths section and our custom properties (like persons) are not allowed by the OpenAPI specification.

persons:
  parameters:
    - $ref: 'commons.yaml#/parameters/userAgent'
  get:
    summary: Gets some persons
    description: Returns a list containing all persons. The list supports paging.
    operationId: searchUsers

We need to add a paths section and put all our path in it using the correct path value as key:

paths:
  '/persons':
      parameters:
        - $ref: 'commons.yaml#/parameters/userAgent'
      get:
        summary: Gets some persons
        description: Returns a list containing all persons. The list supports paging.
        operationId: searchUsers

New errors: Security definition could not be resolved

Once this is done, 2 new errors appear:

  • Security definition could not be resolved: OauthSecurity
  • Security definition could not be resolved: LegacySecurity

Now that we have a valid structure, the paths have been parsed and the parser detected missing security definitions. To solve this error, we add these definitions by referencing the commons.yaml file:

securityDefinitions:
  $ref: commons.yaml#/securityDefinitions

images.yaml and legacy.yaml

For images.yaml and legacy.yaml we do exactly the same things as we’ve done with persons.yaml.

Here the images.yaml file modified (partial view):

swagger: "2.0"
  
info:
  version: 1.0.0
  title: Images Sub-API
  description: images operations

securityDefinitions:
  $ref: commons.yaml#/securityDefinitions

paths: 
  /images:

Here the legacy.yaml file modified (partial view):

swagger: "2.0"
  
info:
  version: 1.0.0
  title: Legacy Sub-API
  description: Legacy operations

securityDefinitions:
  $ref: commons.yaml#/securityDefinitions

paths:
  /js-less-consumer-persons:

Updating the main file

Now that we have modified all sub-files, if we try to edit the main file we get these errors

  • Reference could not be resolved: commons.yaml#/defaultMediatypes
  • Reference could not be resolved: commons.yaml#/defaultSecurity
  • Reference could not be resolved: persons.yaml#/persons
  • Reference could not be resolved: images.yaml#/images-imageId
  • Reference could not be resolved: persons.yaml#/persons-username
  • Reference could not be resolved: persons.yaml#/persons-username-friends
  • Reference could not be resolved: persons.yaml#/persons-username-collecting-items
  • Reference could not be resolved: images.yaml#/images
  • Reference could not be resolved: legacy.yaml#/js-less-consumer-persons

These errors are of 2 types:

  • those due to the commons.yaml structure modification
  • and those due to the persons.yaml, images.yaml and legacy.yaml paths structure modifications

Modifiying commons.yaml references

Before:

consumes:
  $ref: commons.yaml#/defaultMediatypes
produces:
  $ref: commons.yaml#/defaultMediatypes

securityDefinitions:
  $ref: commons.yaml#/securityDefinitions

security:
  $ref: commons.yaml#/defaultSecurity

After:

consumes:
  $ref: 'commons.yaml#/consumes'
produces:
  $ref: 'commons.yaml#/produces'

securityDefinitions:
  $ref: 'commons.yaml#/securityDefinitions'

security:
  $ref: 'commons.yaml#/security'

Modifying paths references

This is the trickyest part of this tutorial. To reference the /persons path in persons.yaml we used persons.yaml#/persons:

paths:
  /persons:
    $ref: 'persons.yaml#/persons'

But persons.yaml structure has changed from :

persons:
  parameters:
    - $ref: 'commons.yaml#/parameters/userAgent'
  get:
    summary: Gets some persons
    description: Returns a list containing all persons. The list supports paging.
    operationId: searchUsers

to:

paths:
  '/persons':
      parameters:
        - $ref: 'commons.yaml#/parameters/userAgent'
      get:
        summary: Gets some persons
        description: Returns a list containing all persons. The list supports paging.
        operationId: searchUsers

The new reference should be something like persons.yaml#/paths//persons but if we try it, the editor shows an error Reference could not be resolved: persons.yaml#/paths//persons. The / in /persons needs to be escaped, how can we do that?

Evaluation of each reference token begins by decoding any escaped character sequence. This is performed by first transforming any occurrence of the sequence ‘~1’ to ‘/’ RFC6901

Before modification:

paths:
  /persons:
    $ref: 'persons.yaml#/persons'
  /js-less-consumer-persons:
    $ref: 'legacy.yaml#/js-less-consumer-persons'
  '/persons/{username}':
    $ref: 'persons.yaml#/persons-username'
  '/persons/{username}/friends':
    $ref: 'persons.yaml#/persons-username-friends'
  '/persons/{username}/collecting-items':
    $ref: 'persons.yaml#/persons-username-collecting-items'
  /images:
    $ref: 'images.yaml#/images'
  /images/{imageId}:
    $ref: 'images.yaml#/images-imageId'

After modification (all / in reference name have been replaced by ~1):

paths:
  /persons:
    $ref: 'persons.yaml#/paths/~1persons'
  /js-less-consumer-persons:
    $ref: 'legacy.yaml#/paths/~1js-less-consumer-persons'
  '/persons/{username}':
    $ref: 'persons.yaml#/paths/~1persons~1{username}'
  '/persons/{username}/friends':
    $ref: 'persons.yaml#/paths/~1persons~1{username}~1friends'
  '/persons/{username}/collecting-items':
    $ref: 'persons.yaml#/paths/~1persons~1{username}~1collecting-items'
  /images:
    $ref: 'images.yaml#/paths/~1images'
  /images/{imageId}:
    $ref: 'images.yaml#/paths/~1images~1{imageId}'

And now the main file and all its sub-files are considered valid by the editor.

Conclusion

You are now ready to split any OpenAPI specification file into valid sub-files in any possible ways. In this 8th part you’ve learned how to use JSON pointers in almost any places in an OpenAPI specification to references items from other files and how to create valid sub-files containing partial informations. In the next final part (at last) we will learn how to extend the OpenAPI specification (coming soon).