Most of the time, just customizing the built-in OpenAPI Rules will get about 90% of the job done, most of the time.
At some point however, there comes a time to customize the rules and perhaps add some new ones using built-in core functions.
Don’t design APIs in a wiki.
Most big companies are guaranteed to have wiki pages in Confluence (or similar), that have HTTP REST APIs that are inconsistently documented, invalid and out-of-date. Nobody reads this content, it’s mostly a waste of time.
This is a terrible way to operate.
Use a RuleSet!
Using a RuleSet will ensure that your APIs are always consistent, valid, clean and useful. RuleSets are a style guide that has been codified into logic.
To create a RuleSet, you simply need to start a new
ruleset.yaml (you can call the file what ever you want) and then
create a new rule under a
rules node at the root of the document. For example:
rules: my-new-rule: description: "check the title is exactly 'hello world'" given: $.info.title severity: error then: function: pattern functionOptions: match: "^hello world$"
Now it’s possible to run this new RuleSet against any vacuum command.
vacuum lint -r ruleset.yaml my-openapi-spec.yaml Every single OpenAPI spec on the planet, should fail this rule; unless the title really is ‘hello world’.
Anatomy of a Rule
vacuum is compatible with Spectral Rule Properties.
given property is a selector. It’s a JSONPath value that identifies where in the
document the node can be found.
It’s similar to XPath, but uses slightly different syntax. JSONPath is not a standard yet, so there are varying implementations of it.
vacuum uses YAML deep down, and to find things using JSONPath, it converts that into YAMLPath using a neat library from my old employer called yaml-jsonpath. To learn more about the syntax vacuum supports, check it out.
Need some help checking a path is valid? Use the JSONPath Evaluator for help to get it right.
severity property is optional, it can be one of the following:
hint. If left blank
the default is
Before we discuss what this is, let’s take a quick look at what a resolver is:
What does resolved actually mean? JSONSchema allows $ref properties. These properties ‘point’ to another schema definition. It allows us to re-use schemas.
These references can be local (in the same file), or they can be remote (another file). Remote can mean both on a completely different file system, or the same file system.
A resolver is responsible for looking up each reference, and then ‘pulling in’ those referenced schemas in-place of the $ref value that has been presented.
A resolved document, is one that has no $ref values anymore, they have all been replaced with the referenced schemas.
But I need an un-resolved document
Sometimes, and for some use cases you do. A good example of this is the no-$ref-siblings Rule that cannot operate correctly if all $ref values have been removed.
If a rule needs to access the raw $ref reference values, set
false allowing the rule to receive
the raw un-resolved version of the spec.
If you leave
resolved blank, it will default to
Generally, resolved documents are going to be what you want to use.
This is the fun part,
then defines what function to run against the JSONPath
given: $.servers[*].url then: function: truthy
There is one required keyword:
field is optional. It’s generally only used for
fieldvalue, the path defined by
givenwill be the node value used.
Some core functions like pattern,
accept arguments via the
functionOptions presents arguments to the function defined to handle the rule.
The pattern function accepts
notMatch arguments, for example:
check-host-rule: description: "Host URL should not contain a trailing slash" given: $.servers[*] then: field: url function: pattern functionOptions: notMatch: "/$"
If your custom RuleSet is the recommended or all RuleSet, you can replace a rule that has already been defined with your own custom rule, or you can ‘override’ individual properties.
For example, to modify the existing oas3-host-host-example rule, to look for something other than ’example.com’, you could add this rule configuration to your custom RuleSet.
extends: [[spectral:oas, recommended]] rules: oas3-host-not-example: description: "check server URL is not testy.mctest-face.com" given: $.servers[*].url then: function: pattern functionOptions: notMatch: "testy\\.mctest-face\\.com"
If you just want the severity of the rule, there is a shortcut.
Changing Rule Severity
If you want to use recommended or all
RuleSets, but you want to change a rule’s severity from a
error to a
warn to trigger a warning, instead of an error.
In this case, you don’t need to re-declare the rule, you can simply add the severity value to the rule name. For example, to change the severity of [operation-operationId] from an error to a warning:
extends: [[spectral:oas, recommended]] rules: operation-operationId: warn
To turn the rule off completely: use
off as the severity, for example:
extends: [[spectral:oas, recommended]] rules: operation-operationId: off
If you already have an existing OpenAPI specification, and you run it through vacuum, chances are that you’re going to see a number of warnings and perhaps some errors.
This overload of data can be a bit much. So the next thing you will probably want to do, turn of all the rules, and enable just the ones you want, as you slowly improve the quality of your specification.
vacuum has a built in no rules RuleSet that turns off everything, (it’s an empty RuleSet). This will allow individual rules to be turned on/enabled.
For example, to enable just the operation-operationId and info-contact Rules, then your RuleSet would look like this:
extends: [[spectral:oas, off]] rules: operation-operationId: true info-contact: true
If you’re going to share your RuleSet (awesome!), you might want to fill out the
documentationUrl properties for
your RuleSet. This is used by vacuum to render links to more details about why the ruleset exists, and what its purpose is.
Often, a description is not enough, and we need docs, use the
extends: [[spectral:oas, off]] documentationUrl: "https://quobix.com/vacuum/rulesets/custom-rulesets#documentation-url" rules: operation-operationId: true info-contact: true
You can also add the URL to your rules as well, so there is more granular access to specific docs:
extends: [[spectral:oas, off]] documentationUrl: "https://quobix.com/vacuum/rulesets/custom-rulesets" rules: oas3-host-not-example: documentationUrl: "https://quobix.com/vacuum/rules/information" description: "check server URL is not testy.mctest-face.com" given: $.servers[*].url then: function: pattern functionOptions: notMatch: "testy\\.mctest-face\\.com"
How To Fix
vacuum adds a new property called
howToFix. This is a string explanation of how to fix the problem when a rule
is violated. The fix is rendered in the console UI and html-report functions.
extends: [[spectral:oas, off]] rules: oas3-host-not-example: howToFix: "Make sure the host name is not example.com, change it!" ...