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.

Adding Rules

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.

lint example:

vacuum lint -r ruleset.yaml my-openapi-spec.yaml

html-report example:

vacuum html-report -r ruleset.yaml my-openapi-spec.yaml

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

The 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

The severity property is optional, it can be one of the following: error, warn, info or hint. If left blank the default is warn.

Resolved

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 resolved to false allowing the rule to receive the raw un-resolved version of the spec.

If you leave resolved blank, it will default to true.

Generally, resolved documents are going to be what you want to use.

Then

This is the fun part, then defines what function to run against the JSONPath defined by given.

given: $.servers[*].url
then:
  function: truthy

There is one required keyword: function. field is optional. It’s generally only used for core functions.

If you omit the field value, the path defined by given will be the node value used.

Some core functions like pattern, schemaand enumeration accept arguments via the functionOptions property.

Function Options

functionOptions presents arguments to the function defined to handle the rule.

The pattern function accepts match or 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: "/$"

Modifying Rules

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

Disabling Rules

To turn the rule off completely: use off as the severity, for example:

extends: [[spectral:oas, recommended]]
rules:
  operation-operationId: off

Enabling Rules

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

Documentation URL

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 documentationUrl property.

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!"
    ...