Whenever you create OpenAPI Specification (OAS) description documents, either generated from code or hand-crafted, for your Web APIs, it is a good practice to validate them with a linter. Not only can you assure that your description documents are OAS compliant, you can also increase readability and maintainability of your OAS description documents and by adhering to the same rules you can ensure that they have the same look-and-feel.
In my development workflow, I use Stoplight’s Spectral for the following reasons:
- it can be integrated into Visual Studio Code, my preferred editor for OAS description documents. Spectral can be configured to give feedback while you are typing
- it has a built-in ruleset to get you started, but can be customized and extended with custom rules
- it has as a Command Line Interface (CLI), making it possible to integrate Spectral into a CI/CD pipeline
- it has a JavaScript API. This is essential if you want to write tests to validate the working of your custom rules
Setup Spectral Visual Studio Code extension
If you use Visual Studio Code (VS Code), you can integrate Spectral by installing its VSCode extension either via the Marketplace website or via VS Code’s Extensions View (Ctrl+Shift+X).
To experience how Spectral works with VS Code, create a folder (to follow along, name it oas
) to hold your OAS description documents, and create a file named minimal-oas3.yaml
in the folder. In the editor window, enter the following:
openapi: 3.0.3
You will now see that the entered text is underlined with a squiggly line, which indicates there are issues. To see the issues, hover your mouse over the text or open the problems panel.
Setup Spectral CLI for your Web API development project
The Spectral CLI is a node module. To be able to use it, Node.js must be installed. When this prerequisite is met, install Spectral on your machine using the following statement:
npm install -g @stoplight/spectral
Spectral can also be installed as a development dependency for your Web API project. To be able to do this, a package.json
file must be created for your project. Use the following statement if one must be created:
# create a package.json file for your project to keep track of the dependent node modules
npm init -y
Install Spectral as a development dependency with the following statement:
# install Spectral as development dependency
npm install --save-dev @stoplight/spectral
Lint the minimal-oas3.yaml
file created in Visual Studio Code with the following statement:
spectral lint ./oas/minimal-oas3.yaml
Spectral will return with Exit Status 1 and the same error and warning messages as in Visual Studio Code
1:1 warning info-contact Info object should contain `contact` object.
1:1 warning info-description OpenAPI object info `description` must be present and non-empty string.
1:1 warning oas3-api-servers OpenAPI `servers` must be present and non-empty array.
1:1 error oas3-schema Object should have required property `info`.
1:1 warning openapi-tags OpenAPI object should have non-empty `tags` array.
✖ 5 problems (1 error, 4 warnings, 0 infos, 0 hints)
To solve the error, append the following lines to the minimal-oas3.yaml
file:
info:
title: Minimal OAS 3.0.x description document
version: 1.0.0 - Alfa
paths: {}
Create a ruleset file for your project
To override Spectral’s built-in ruleset and/or to extend it with your own rules, you have to create a ruleset file for the project. Name the file .spectral.yml
and put it in the project’s root folder if you want to use the default Ruleset file
setting of both Spectral’s VS Code extension and CLI.
Add the following lines to use Spectral’s built-in ruleset as baseline for your own ruleset:
extends:
- spectral:oas
To suppress the current warnings for the minimal-oas3.yaml
file in both Visual Studio Code and the CLI, append the following lines to the .spectral.yml
file:
rules:
oas3-api-servers: off
openapi-tags: off
info-contact: off
info-description: off
Remove these suppressions so that we can verify that the warnings will show up in GitHub after the next step is implemented.
Lint OAS3 description documents when they are pushed to GitHub
If you use GitHub as your git provider, a GitHub Actions workflow can be created to lint the OAS3 description documents whenever changes to these documents are pushed to GitHub. To define the lint workflow for our project, create a file named lint-oas3.yaml
in the .github/workflows
directory and add the following lines:
name: lint OAS3 description documents with Spectral
on:
push:
paths:
# lint all OAS3 description documents if changes to the following files are pushed to remote
- ./oas/**/*.yaml
- .github/workflows/lint-oas3.yaml
- .spectral.yaml
workflow_dispatch:
# enable 'trigger workflow manually'
jobs:
spectral-lint:
runs-on: ubuntu-latest
steps:
# checkout the repository, so the next steps can access the files in the repository
- uses: actions/checkout@v2
# install node.js required to run Spectral
- uses: actions/setup-node@v1
with:
node-version: '12'
# install Spectral
- run: npm install
# run the oas:lint script
- run: npm run oas:lint
Also, create a script named oas:lint
in the package.json
file to simplify the invocation of Spectral:
{
...
"scripts": {
...
"oas:lint": "spectral lint ./oas/*"
...
}
...
}
Push the files we have created up to now to GitHub, then navigate to the website of the GitHub repository and open the Actions page to get an overview of the runs of the workflows of your GitHub repository.
Click on a workflow run to see the summary of the run
In the run summary, the same Spectral warnings are shown as in VS Code and in Spectral CLI.
Lint OAS description documents before a commit
If you want to fail fast to learn fast, you don’t want to wait until your commits are pushed to the remote repository to find out that your changes contains errors or do not comply. Luckily there is tooling to help prevent bad commits. With Husky, you can run a pre-commit script to lint OAS description documents every time a commit is performed. To setup Husky:
# install husky as development dependency
npm install --save-dev husky
Add the following lines to the package.json
file to configure Husky:
{
...
"scripts": {
...
"oas:lint-warning-as-errors": "spectral lint -F warn ./oas/*"
...
},
...
"husky": {
"hooks": {
"pre-commit": "npm run oas:lint-warning-as-errors"
}
}
...
}
The script oas:lint-warning-as-errors
is added to verify the correct setup of Husky. This script runs Spectral with the fail severity option set to warn
(-F warn) so that Spectral would return a failure exit code when there are warnings found for the linted OAS description documents. This will trigger Husky to cancel the commit.
Change the pre-commit script to npm run oas:lint
and stage this change to commit your Husky configuration.
Create custom rules
Spectral provides a Domain Specific Language for writing custom rules. Checkout Spectral’s Custom Rulesets documentation to learn more about the language syntax. In essence, a rule consists of:
- a name to identify the rule
- a
given
property that consists of JSONPath expressions that is used to find the elements the rule must be applied to - a
then
property that indicates the function that must be applied on the found elements
Rules must be added under the rules
property of a ruleset. Below is an example of a rule. This rule checks that the name of every schema definition (found with: $.components.schemas.*~) is written in pascal case (first letter of each word in a compound word is capitalized).
rules:
schema-names-pascal-case:
description: Schema names MUST be written in PascalCase
message: "component '' "
severity: error
given: '$.components.schemas.*~'
then:
function: casing
functionOptions:
type: pascal
The following online tools are indispensable when writing custom rules:
- yaml to json converter to transform (part of) a OAS description document to JSON format
- JSONPath Online Evaluator to test JSONPath expressions on the transformed OAS description documents
When you have implemented a new rule, it is important to test it extensively to minimize the chance of getting false positives (the rule finds an issue while there is no issue) and false negatives (the rule doesn’t find an issue while there is an issue). Luckily Spectral exposes a JavaScript API, so it is possible to write code to test the implementation of a rule.
To kill two birds with one stone, we will write the tests with Gherkin. Even though, you can provide documentation via the description property of a rule, it is much more clearer to use examples to describe the intent of a rule. With Gherkin we can write these examples in natural language so that it is understandable for everyone.
Below is the content of the schema-names-pascal-case
feature file where the working of the schema-names-pascal-case
rule is described using examples or scenarios. The first scenario shows what a schema name written in pascal case looks like and the second scenario shows schema names that are not written in pascal case. In the feature file, you can also see the usage of Gherkin keywords like Background
and Scenario Outline
to minimize repetitions in scenario’s. Check the documentation of Gherkin to learn more about the syntax
Feature: Schema names Must be written in PascalCase
Background:
Given the rule 'schema-names-pascal-case'
Scenario: Schema name is written in PascalCase
Given the OAS3 description document
"""
openapi: 3.0.3
components:
schemas:
PascalCaseComponent:
type: object
"""
When the OAS3 description document is linted
Then there should be no errors
Scenario Outline: Schemas name is not written in PascalCase
Given the OAS3 description document
"""
openapi: 3.0.3
components:
schemas:
<component name>:
type: object
"""
When the OAS description document is linted
Then the error message should be
"""
component '<component name>' must be pascal case
"""
Examples:
| component name |
| camelCaseComponent |
| snake_case_component |
| kebab-case-component |
| Mix-Case-Component |
These specifications can be turned into executable specifications using Cucumber Open. The JavaScript version of Cucumber Open can be installed using the following statement:
npm install --save-dev @cucumber/cucumber
After installation, configure cucumber by creating a file named cucumber.js
in the project’s root folder with the following lines:
module.exports = {
default: '--publish-quiet'
}
Create a folder named features
to store the feature files and create a sub-folder named step-definitions
where the step definition files are stored. A step definition is the mapping of a Gherkin step with code that must be executed for the step. Below is the content of the stepdefs.js
file with the step definitions for the steps used in the scenario’s described above.
Given('the rule {string}', function(rule) {
this.rule = rule;
});
Given('the OAS3 description document', function(oas3Document) {
this.document = new Document(oas3Document, Parsers.Yaml);
});
When('the OAS description document is linted', async function() {
const spectral = new Spectral();
spectral.registerFormat('oas3', isOpenApiv3);
await spectral.loadRuleset([
join(__dirname, '../../spectral_rules/' + this.rule + '.yaml')
]);
this.results = await spectral.run(this.document);
});
Then('there should be no errors', function() {
this.results.should.have.lengthOf(0, JSON.stringify(this.results, null, "\t"));
});
Then('the error message should be', function(expected) {
this.results.should.have.lengthOf(1, JSON.stringify(this.results, null, "\t"));
this.results[0].code.should.equal(this.rule, JSON.stringify(this.results, null, "\t"));
this.results[0].message.should.equal(expected, JSON.stringify(this.results, null, "\t"));
});
To ensure that the test results are correct, the rules must be tested in isolation. To achieve this, every rule is put in its own ruleset file. An additional advantage of this isolation is that you can create OAS description documents with only the properties that are needed to validate the rule. The ruleset files are stored in the spectral_rules
folder.
Additionally, install the Chai Assertion Library to increase the expressiveness and readability of the step definitions code
npm install --save-dev chai
Without Chai, the assertion this.results.should.have.lengthOf(0, JSON.stringify(this.results, null, "\t"));
would have been written as assert.equal(this.results.length, 0, JSON.stringify(this.results, null, "\t"))
.
To extend your ruleset with your new rule, add the path to the rule file below the extends
property:
extends:
- spectral:oas
- ./spectral_rules/schema-names-pascal-case.yaml
Conclusion
Spectral makes it easy to integrate an OAS linter in your Web API development workflow. With Spectral’s DSL you can implement your own rules and you can automate validation of these rules using Spectral’s JavaScript API. And if you write these tests with Gherkin, these tests can become the living documentation of your rules and ultimately the documentation of your API design style guide. Feel free to check my GitHub repository for more examples of feature files written to verify custom Spectral rules.