OpenLint 101

An OpenLint ruleset tells a linter where to look in a JSON or YAML document, and what must be true there. This page teaches the format through six small examples, each one tested against a real linter.

Every example lints this small OpenAPI document. Save it as openapi.yaml to follow along:

openapi: 3.1.0
info:
  title: Example API
  version: 1.0.0
servers:
  - url: http://api.example.com
paths:
  /userAccounts:
    get:
      summary: List every user account in the system, with paging, filtering and sorting options
      responses:
        "200":
          description: OK

To run a ruleset, save it as .spectral.yaml and run:

npx @stoplight/spectral-cli lint openapi.yaml --ruleset .spectral.yaml

OpenLint's own command-line tool hasn't been released yet. Until it is, the Spectral CLI runs these rulesets, because OpenLint rulesets are Spectral rulesets.

1. Your first rule

A ruleset is a map of rules. Each rule has two parts: given says where to look, as a JSONPath expression, and then says what must be true there, by calling a function.

rules:
  info-description:
    description: Every API needs a description.
    given: $.info
    then:
      field: description
      function: truthy

This rule looks at $.info, checks its description field with the truthy function, and reports when it's missing:

openapi.yaml:2:6 warning info-description "Every API needs a description."

With no message of its own, the result uses the rule's description.

2. Severity

Each result has a severity: error, warn (the default), info or hint. Use off to switch a rule off. Errors are what usually fail a CI build.

rules:
  info-description:
    description: Every API needs a description.
    severity: error
    given: $.info
    then:
      field: description
      function: truthy
openapi.yaml:2:6 error info-description "Every API needs a description."

3. Start from a built-in ruleset

You don't have to write every rule yourself. extends pulls in another ruleset, here the built-in OpenAPI rules, and rules then adjusts them by name: raise one to error, or turn one "off".

extends: spectral:oas
rules:
  info-contact: "off"
  operation-tags: error
openapi.yaml:2:6 warning info-description "Info "description" must be present and non-empty string."
openapi.yaml:9:9 warning operation-description "Operation "description" must be present and non-empty string."
openapi.yaml:9:9 warning operation-operationId "Operation must have "operationId"."
openapi.yaml:9:9 error operation-tags "Operation must have non-empty "tags" array."

Quote "off". Some YAML tools read a bare off as false.

4. Functions

The function in then decides what "must be true" means. The core functions are truthy, falsy, defined, undefined, pattern, casing, length, alphabetical, enumeration, schema, xor, or and unreferencedReusableObject. functionOptions configures them. field: "@key" checks a key instead of a value.

rules:
  paths-kebab-case:
    description: Path segments use kebab-case.
    given: $.paths
    then:
      field: "@key"
      function: pattern
      functionOptions:
        match: "^(/[a-z0-9-{}]+)+$"
  operation-summary-length:
    description: Summaries stay short.
    given: $.paths[*][get,put,post,delete,patch]
    then:
      field: summary
      function: length
      functionOptions:
        max: 80
openapi.yaml:8:17 warning paths-kebab-case "Path segments use kebab-case."
openapi.yaml:10:16 warning operation-summary-length "Summaries stay short."

5. Formats and messages

formats limits a ruleset or a rule to one kind of document, so OpenAPI rules don't fire on an AsyncAPI file. message writes the result text, with placeholders such as {{property}} and {{value}}.

formats: [oas3]
rules:
  servers-https:
    description: Servers must use HTTPS.
    message: "{{property}} must start with https:// but was {{value}}"
    severity: error
    given: $.servers[*]
    then:
      field: url
      function: pattern
      functionOptions:
        match: "^https://"
openapi.yaml:6:10 error servers-https "url must start with https:// but was http://api.example.com"

6. Aliases

When several rules look in the same place, name that place once as an alias, and refer to it with #.

aliases:
  Operation:
    - $.paths[*][get,put,post,delete,patch]
rules:
  operation-summary:
    description: Every operation has a summary.
    given: "#Operation"
    then:
      field: summary
      function: truthy
  operation-id:
    description: Every operation has an operationId.
    given: "#Operation"
    then:
      field: operationId
      function: truthy
openapi.yaml:9:9 warning operation-id "Every operation has an operationId."

The operation has a summary, so only the operationId rule reports.

Check your ruleset

The JSON Schema catches mistakes in a ruleset, such as a misspelled field or a given that isn't a path, before you lint anything. Using the schema shows how to use it in your editor and in CI.

Where next