Writing OpenAPI (Swagger) Specification Tutorial Series - Part 8
Splitting specification file
By Arnaud Lauret, August 2, 2016
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 (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 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
Personsdefinition
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):
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:
You now can start a web service on any folder:
or
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:
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:
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:
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:
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…
… 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:
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:
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:
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:
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:
The username parameter is no longer in the main file and is not in the paths.yaml file but in the parameters.yaml file:
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:
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):
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:
And then we reference this single value in both produces and consumes:
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-usernamefor/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:
Missing info property
We add an short info section after swagger:
Missing paths property
As we do not define any path in this file we just need to add an empty paths section:
Property defaultSecurity not allowed
As the defaultSecurity correspond to the OpenAPI security section we just need to rename it:
Invalid commons.yaml file:
Valid commons.yaml file:
Property defaultMediatypes not allowed
defaultMediaType is not a valid OpenAPI property:
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:
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:
by this one:
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:
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.yamlstructure modification - and those due to the
persons.yaml,images.yamlandlegacy.yamlpaths 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:
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 information. In the next final part (at last) we will learn how to extend the OpenAPI specification.
