Custom OpenAPI Style Rules with Spectral

Edit: There’s a newer post on this topic using Redocly CLI.

I work quite a bit with OpenAPI specs and with lots of specs and lots of collaborators, keeping the specs all functional (never mind tidy, consistent, or other dreamwords) is a challenge! We use spectral to check our specs, both when we work on them and in the build process. Spectral is great but it has Opinions(TM)!

For most users, running Spectral out of the box gives quite a lot of output even on an otherwise valid spec. I do think the default ruleset for Spectral is pretty good, but every situation is different so having your own ruleset to use is a good idea. This post shows how to use a ruleset and some examples.

Disabling Spectral Rules

(All these examples are for OpenAPI version 3.0.0 or later)

Spectral (and if you’re using Stoplight Studio this as well) will pick up a file called .spectral.yml in the root of the project. In this file we can pick which style rules to follow, enable/disable rules and add our own rules. For a very simple starting point, try this:

extends: spectral:oas
rules:
  openapi-tags: false
  operation-tags: false

Spectral is very keen that every endpoint should have a tag but in fact this isn’t required by the spec. I work with a large number of small API specs and most don’t have tags (since we’d just tag them all the same thing) so by using the spectral:oas ruleset as a starting point, then disabling the two tag-related rules, I can run Spectral to get the benefit of all the other checks without getting error output from these tag rules.

To know which rules to disable, check your output. Spectral gives very detailed error information including which rule was included and which line of the spec it happened on. It’s your choice whether to fix the spec or silence the rule :)

Custom Functions

What happens when you need to add rules that don’t already exist? You make your own!

Spectral has some good built in functions that you can use and re-use as needed. For example, here is a rule I’m using to check that the version field in the info block is actually a version number (after we discovered one with version set to “beta” that broke some of our automated tagging stuff!)

rules:
  # Require 3-part Semantic Versions as the spec versions
  semver:
    severity: error
    recommended: true
    message: Specs should follow semantic versioning. {{value}} is not a valid version.
    given: $.info.version
    then:
      function: pattern
      functionOptions:
        match: "^([0-9]+.[0-9]+.[0-9]+)$"

Add this to the spectral.yml file and it will pick up any spec where the version field in the info block isn’t a number in the format x.x.x – very handy!

One thing that tripped me up was the JSON Path expressions needed to refer to different parts of the spec so here is a more deeply nested example: checking that all the response fields (that we always put in components/schemas) are in snake_case.

  response-property-names:
    severity: warn
    message: "Invalid response property name: {{property}} (expected: lowercase with underscores)"
    recommended: true
    given: "$..components.schemas.*.properties[*]~"
    then:
      function: pattern
      functionOptions:
        match: "^[a-z_]+$"

By turning off the rules we don’t need (or aren’t ready to meet yet!), the style checking features of Spectral become much more useful. With the ability to use the tools locally and in our build chain (and of course included in Stoplight Studio), that makes it very easy for all our contributors to do the “right” thing rather than make changes and then have either me or the pull request build criticise their hard work!! Tools like this really improve the quality of the OpenAPI specs – but the detail can be complicated. Hopefully sharing this post helps a bit (and I know I’ll be using it for reference in the future too).


Also published on Medium.

Leave a Reply

Please use [code] and [/code] around any source code you wish to share.

This site uses Akismet to reduce spam. Learn how your comment data is processed.