OpenLint Ruleset Specification

Working Draft 1.x,

This version:
https://spec.openlint.org/v1/draft/
Issue Tracking:
GitHub
Inline In Spec
Editor:
OpenLint maintainers
All versions:
https://spec.openlint.org/
Working draft

This is the working draft of 1.x. It changes without notice as the OpenLint community works on it. Nothing in it is released. Releases are immutable and listed at spec.openlint.org.


Abstract

A ruleset is a set of rules for checking JSON and YAML documents. Each rule selects values in a document and checks each selected value with a function. This specification defines the ruleset format, how a linter applies a ruleset to a document, and the results it reports, so that a ruleset means the same thing in every conforming linter.

1. Introduction

A ruleset is a set of rules for checking JSON and YAML documents. Each rule selects nodes in a document and checks each selected node with a function. A ruleset records the conventions a group of documents has to follow, so that the documents are checked the same way wherever they are written or reviewed. Nothing in the format depends on what the documents describe.

This specification defines the format apart from any one linter, so that several linters can implement it and agree on what a ruleset means.

1.1. How to read this document

The normative content of this document is its requirements, together with the definitions in § 3 Terminology, the conformance classes in § 2.2 Conformance classes, the tables that requirements refer to, and the ruleset schema that /req/ruleset/schema refers to. Everything else, including this introduction, examples, notes, and issues, is informative.

A requirement looks like this:

/req/example/identifiedrulesetexample

A requirement MUST be written so that a reader can tell, from the requirement alone, what conforms and what does not.

Each requirement carries:

A sentence without a keyword inside a requirement defines a term the keyword sentence uses, and binds with it.

The example above only shows the shape. Its status, example, marks that it is not a requirement of this specification. Recommendations (/rec/, written with should) and permissions (/per/, written with may) look the same and belong to a class too.

Every requirement is listed by class in Appendix A: Requirements index, and every test with its fixtures in Appendix B: Conformance tests. Open questions appear as issue blocks where they arise, and the Issues Index at the end of the document collects them. A release carries no issues.

1.2. Versions of this document

Releases are numbered as Semantic Versioning 2.0.0 [SEMVER] prescribes. A patch release corrects the text or the tests without changing what conforms, a minor release adds requirements that every ruleset conforming to the previous release still satisfies, which can include new requirements for linters, and a major release is anything else. Each release has its own URL and never changes once published.

What counts as a change that breaks conformance, and what is promised to whom, is discussed in discussion #12.

URL Serves
https://spec.openlint.org/<version>/ A release, such as 1.0.0.
https://spec.openlint.org/v<major>/draft/ The working draft of a major version, such as v1 for 1.x. It changes without notice.
https://spec.openlint.org/v<major>/ The latest release of a major version. Until the major version has a release, only the working draft’s ruleset.schema.json is served there.
https://spec.openlint.org/ An index of every version.

Each version publishes its own copy of the ruleset schema next to the document, as ruleset.schema.json. That copy is the one /req/ruleset/schema refers to, and its $id is its own URL. Editors and validators that follow the newest release can use https://spec.openlint.org/v<major>/ruleset.schema.json.

2. Conformance

2.1. Requirement keywords

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

These keywords appear only inside identified requirements. Text outside a requirement carries no normative force, whatever words it uses.

2.2. Conformance classes

A conformance target is the kind of thing a requirement applies to, either a ruleset or a linter.

ruleset

A ruleset document. A ruleset conforms when it satisfies every requirement whose target is ruleset.

linter

A linter. A linter conforms when it satisfies every requirement whose target is linter.

A conformance class is a set of requirements for one conformance target. An implementation claims conformance to a class as a whole. Each class below names the classes it depends on. When it depends on a class of the same target, conforming to it includes conforming to that class.

A conformance claim names a release of this specification and the classes claimed, as https://spec.openlint.org/<version>/#/conf/<name>. A later release can add requirements to a class, so a claim holds for the release it names.

/conf/ruleset-corerulesetdepends on none

The ruleset format: what a ruleset document has to look like to be loaded by any conforming linter. Most of it is one requirement, validity against the ruleset schema. The other requirements are constraints a schema cannot express.

8 requirements, 2 recommendations, 1 permission, 86 tests. Tests, index.

/conf/linter-corelinterdepends on ruleset-core

Everything a linter does with a ruleset and a document: loading, parsing and resolution, rule state, selecting documents and nodes, applying functions, and the results and messages it reports. It depends on ruleset-core because a linter rejects a ruleset that does not conform to that class. § 6.2 Loading a ruleset says how.

79 requirements, 1 recommendation, 144 tests. Tests, index.

/conf/linter-custom-functionslinterdepends on linter-core

The contract of custom functions, in any language: how a linter resolves a function a ruleset names, calls it, and reports what it returns. A linter claims it together with a binding.

4 requirements, 5 tests. Tests, index.

/conf/linter-custom-functions-javascriptlinterdepends on linter-custom-functions

The JavaScript binding of custom functions: how a custom function is written as a JavaScript module, and where a linter finds it.

2 requirements, 3 tests. Tests, index.

2.3. Requirement identifiers

A requirement identifier has the form /req/<topic>/<name>. The topic is what the requirement is about, such as a ruleset key (given, overrides) or a step of linting (resolution, result), not a section of this document, so a requirement keeps its identifier when the document is reorganized. The name describes the requirement. Both are lowercase words joined by hyphens.

Identifiers are permanent:

Whether path-style identifiers work for everyone is an open question in discussion #13. Alternatives such as numeric identifiers (REQ-042) or dotted names (given.no-match) have not been raised there yet.

2.4. Conformance tests

Every requirement has at least one conformance test, identified as /conf/<topic>/<name> in the same way. A test is data: a ruleset fixture, for linter tests a document fixture, and the expected outcome. Appendix B: Conformance tests renders every test from that data, and a linter is checked against the same data. A test covers one or more requirements of one class.

A test for a ruleset requirement expects a linter to load or to reject the fixture. A test for a linter requirement expects the results of linting the document fixture with the ruleset fixture, compared on the fields it names.

The tests follow these rules:

Whether these tests become a published conformance suite, where its runner would live, and how a linter would publish its results against it, is not decided.

3. Terminology

document

A JSON [RFC8259] or YAML [YAML-1.2.2] document that a linter lints. After parsing, both are the same tree of objects, arrays, strings, numbers, booleans, and null.

ruleset

A JSON or YAML document in the format defined by this specification: a set of rules, the rulesets it extends, and adjustments to both.

rule

A named unit of a ruleset that selects nodes in a document with given and asserts something about each of them with then.

rule name

The key under which a rule appears in rules. The name identifies the rule. It appears in results, and an extending ruleset uses it to adjust the rule.

path expression

A string in given that selects nodes: a JSONPath expression beginning with $, or an alias reference beginning with #.

alias

A named, reusable set of path expressions, declared under aliases.

alias reference

A path expression of the form #<name> followed by an optional JSONPath remainder, such as #Info.description.

matched node

A node of the document selected by one of a rule’s path expressions.

function

A named assertion applied to a matched node by then. A function is either one of the core functions or a custom function.

custom function

A function whose implementation a ruleset supplies as code, named under functions.

result

What a linter reports about a document: a problem a function found in a matched node, or a problem the linter found itself, such as a syntax error. A result has a code, a message, a severity, and a location in the document. See § 6.8 Results.

severity

How a rule’s results are reported: error, warn, info, or hint. The severity off disables the rule.

effective severity

The severity a rule has after every ruleset in the chain of extends and every override has adjusted it.

enabled

The state of a rule that a linter evaluates. A rule is either enabled or not, independently of its severity. A rule with effective severity off is not enabled.

format

A named kind of document, such as oas3_1 for an OpenAPI 3.1 description. A rule scoped to formats runs only against documents of those kinds.

override

An entry of overrides: adjustments that apply to the subset of documents matched by its files globs.

resolved document

The document after every $ref has been replaced by the value it points to, including references into other files. See § 5.3 Reference resolution.

extension property

A property whose name begins with x-, carried by the format for the author’s own tooling and ignored by a linter.

linter

Software that takes a ruleset and one or more documents and produces results.

shorthand

A value in rules that is not a Rule Object: a severity name or a boolean, which adjusts a rule the ruleset inherits instead of defining one.

mode

The second element of an extends entry, recommended, all, or off, which decides which of the inherited rules start out enabled.

file-scoped override

An override whose files globs do not all carry a JSON Pointer fragment. It can change anything a ruleset declares, for the documents it matches.

pointer-scoped override

An override whose files globs all carry a JSON Pointer fragment. It can only change severities, at the locations the pointers name.

core function

A function this specification defines, which every conforming linter provides under its name.

function invocation

One entry of a rule’s then: the function to apply, the field to apply it to, and its functionOptions.

problem

What a function returns for a value that fails: a message, and optionally the path of the offending value. A linter reports each problem as a result.

requirement

An identified statement of this specification that one conformance target has to satisfy to conform. Recommendations and permissions are identified the same way but are not requirements.

4. The ruleset format

This part defines what a ruleset document looks like, in the order of the document itself: the document, the Ruleset Object at its root and each of its properties, the Rule Object and each of its properties.

4.1. The ruleset document

A ruleset is one file, written in JSON or YAML.

/req/ruleset/documentrulesetruleset-coresince this draft

A ruleset MUST be a JSON [RFC8259] or YAML [YAML-1.2.2] document.
3 tests
/conf/ruleset/document-json A JSON text whose root is an object is valid. valid
/conf/ruleset/document-object A YAML document whose root is an object is valid. valid
/conf/ruleset/document-syntax-error A ruleset that is neither JSON nor YAML, here an unclosed flow sequence, is invalid. invalid

/req/ruleset/single-documentrulesetproposedruleset-coresince this draft

A ruleset written in YAML MUST contain exactly one YAML document.
2 tests
/conf/ruleset/document-single-yaml-document A YAML stream with one document, with an explicit document marker, is valid. valid
/conf/ruleset/document-two-yaml-documents A YAML stream with two documents is invalid. invalid

/req/ruleset/single-document is proposed, because it changes current behavior. Spectral and OpenLint accept a YAML file with several documents separated by ---, read the first, and drop the others without reporting them; neither documents this. A ruleset split by a stray --- therefore loses rules silently, and rejecting the file catches that. Until both linters change, /conf/ruleset/document-two-yaml-documents fails against them. Whether this counts as a breaking change for 1.x is part of discussion #12.

The ruleset schema is written in JSON Schema 2020-12 and published with each version of this specification, next to this document as ruleset.schema.json. It validates the value at the root of the document.

/req/ruleset/schemarulesetruleset-coresince this draft

The root value of a ruleset MUST be valid against the ruleset schema of this version of this specification, as JSON Schema 2020-12 [JSON-SCHEMA-CORE-2020-12] [JSON-SCHEMA-VALIDATION-2020-12] defines validity.
66 tests
/conf/aliases/
/conf/aliases/name-valid An alias name of letters, digits, underscores, and hyphens starting with a letter is valid. valid
/conf/aliases/scoped-form-valid A scoped alias whose every target has formats and given is valid. valid
/conf/aliases/simple-form-valid A simple alias that is an array of path expressions is valid. valid
/conf/aliases/name An alias name starting with a digit is invalid. invalid
/conf/aliases/scoped-form-missing-formats A scoped alias target without formats is invalid. invalid
/conf/aliases/simple-form-string A simple alias given as a bare string is invalid. invalid
/conf/custom-functions/
/conf/custom-functions/declaration-valid functions as an array of strings with a functionsDir string is valid. valid
/conf/custom-functions/declaration-not-array A functions value that is a string is invalid. invalid
/conf/extends/
/conf/extends/entry-forms-valid A string entry and tuples with each of the three modes are valid. valid
/conf/extends/entry-tuple-length An extends tuple with a third element is valid; elements after the mode are ignored. valid
/conf/extends/shape-array A ruleset whose extends is an array mixing a string and a tuple is valid. valid
/conf/extends/shape-string A ruleset whose extends is a single string is valid. valid
/conf/extends/entry-invalid-mode An extends tuple with the mode strict is invalid. invalid
/conf/extends/shape-object A ruleset whose extends is an object is invalid. invalid
/conf/formats/
/conf/formats/shape-valid formats as an array of strings on the ruleset, a rule, an override, and an alias target is valid. valid
/conf/formats/shape-string A ruleset whose formats is a string is invalid. invalid
/conf/formats/unknown-identifier A rule whose formats names an identifier outside the table, oas31, is invalid. invalid
/conf/given/
/conf/given/path-expression-forms A JSONPath expression starting with $ and an alias reference starting with # are both valid. valid
/conf/given/present A rule with given and then is valid. valid
/conf/given/shape-valid given as a single path expression and as an array of them are both valid. valid
/conf/given/bare-property-name A rule whose given is a bare property name is invalid. invalid
/conf/given/required A rule with then but no given is invalid. invalid
/conf/given/shape-empty-array A rule with given: [] is invalid. invalid
/conf/given/shape-object A rule whose given is an object is invalid. invalid
/conf/overrides/
/conf/overrides/file-scoped-valid A file-scoped override with rules, extends, formats, parserOptions, and aliases is valid. valid
/conf/overrides/mixed-globs An override whose files mix a pointer glob and a plain glob is file-scoped, so it can define rules with shorthands and is valid. valid
/conf/overrides/pointer-scoped-valid A pointer-scoped override whose rules are severities only is valid. valid
/conf/overrides/shape-valid An override with a non-empty files array is valid. valid
/conf/overrides/file-scoped-empty A file-scoped override with neither extends nor rules is invalid. invalid
/conf/overrides/pointer-scoped-extends A pointer-scoped override with extends is invalid. invalid
/conf/overrides/pointer-scoped-rule-object A pointer-scoped override that defines a rule object is invalid. invalid
/conf/overrides/shape-empty An empty overrides array is invalid. invalid
/conf/overrides/shape-no-files An override without files is invalid. invalid
/conf/parser-options/
/conf/parser-options/shape-valid Both parser options with severity values are valid. valid
/conf/parser-options/shape-extension-property An extension property inside parserOptions is invalid: the permission covers the root and rules only, and parserOptions admits no other properties. invalid
/conf/parser-options/shape-unknown-option An unknown parser option is invalid. invalid
/conf/parser-options/shape-unknown-severity duplicateKeys: critical is invalid. invalid
/conf/rule/
/conf/rule/all-properties A rule using every property with its documented type is valid. valid
/conf/rule/then-present A rule with then and given is valid. valid
/conf/rule/recommended-not-boolean A rule whose recommended is a string is invalid. invalid
/conf/rule/then-required A rule with given but no then is invalid. invalid
/conf/rule/type-unknown A rule whose type is neither style nor validation is invalid. invalid
/conf/rule/unknown-property A rule with the unknown property severty is invalid. invalid
/conf/ruleset/
/conf/ruleset/all-properties A ruleset using every root property with its documented type is valid. valid
/conf/ruleset/contributes-extends-only A ruleset with only extends is valid. valid
/conf/ruleset/extension-root-property A ruleset with the extension property x-owner at the root is valid. valid
/conf/ruleset/contributes-empty A ruleset with only description is invalid. invalid
/conf/ruleset/description-not-string A ruleset whose description is a number is invalid. invalid
/conf/ruleset/document-root-array A ruleset whose root value is an array is invalid. invalid
/conf/ruleset/documentation-url-intranet A ruleset whose documentationUrl names a single-label intranet host, http://wiki/docs, is invalid. invalid
/conf/ruleset/documentation-url-invalid A ruleset whose documentationUrl is not a URI is invalid. invalid
/conf/ruleset/documentation-url-mailto A ruleset whose documentationUrl is a mailto: URI is invalid: only http, https, and ftp URLs are accepted. invalid
/conf/ruleset/rules-not-object A ruleset whose rules is an array is invalid. invalid
/conf/ruleset/unknown-root-property A ruleset with rules and the unknown root property rule is invalid. invalid
/conf/severity/
/conf/severity/values-name A rule with severity: hint is valid. valid
/conf/severity/values-number A rule with severity: 3 is valid. valid
/conf/severity/values-invalid A rule with severity: critical is invalid. invalid
/conf/shorthands/
/conf/shorthands/values-valid Every severity name and both booleans are valid in the shorthand position. valid
/conf/shorthands/numeric A numeric severity in the shorthand position is invalid. invalid
/conf/shorthands/unknown-name An unknown name in the shorthand position is invalid. invalid
/conf/then/
/conf/then/invocation-form-valid An invocation with function, field, and functionOptions of the right types is valid. valid
/conf/then/shape-empty-array A rule with then: [] is valid. valid
/conf/then/shape-valid then as a single invocation and as an array of them are both valid. valid
/conf/then/function-missing An invocation without function is invalid. invalid
/conf/then/options-not-object An invocation whose functionOptions is a string is invalid. invalid
/conf/then/shape-string A rule whose then is a string is invalid. invalid

documentationUrl takes only an absolute http, https, or ftp URL with a public host, which is how the url format of OpenLint’s validator reads a URL. The schema spells that out as a pattern. It rejects mailto: and urn: URIs, localhost, single-label intranet hosts such as http://wiki/docs, private and loopback addresses, and punycode host names; see /conf/ruleset/documentation-url-intranet. Accepting any absolute URI [RFC3986] would let a ruleset link to internal documentation.

4.2. The Ruleset Object

The Ruleset Object is the root of a ruleset. It holds the rules the ruleset defines, the rulesets it extends, and the adjustments it makes to them, and it can have a description and a documentationUrl.

Property Type Description
description string Human-readable description of what the ruleset checks.
documentationUrl string (URL) URL to documentation for this ruleset. A rule that declares no documentationUrl of its own reports this URL followed by # and the rule name.
formats array of strings, see Formats Document formats this ruleset applies to. Acts as the default for every rule that does not declare its own formats. When omitted, rules apply to every document.
extends string or array of strings or arrays, see Extends Other rulesets this ruleset inherits from.
rules map of objects or strings or booleans, see Rule Object or Shorthand Maps a rule name to a Rule Object, or to a shorthand that adjusts a rule inherited through extends. The name appears in results, and an extending ruleset uses it to adjust the rule.
aliases map of objects or arrays of strings, see Aliases Named, reusable path expressions that rules can reference from given with a leading #.
overrides array of objects, see Overrides File-scoped and pointer-scoped adjustments applied on top of the ruleset.
parserOptions object, see Parser options Severities for problems detected while parsing the document itself, before any rule runs.
functions array of non-empty strings Names of the custom functions the ruleset uses, loaded from functionsDir.
functionsDir string Directory, relative to the ruleset file, that custom functions named in functions are loaded from. Defaults to functions.
x-* any Extension property.

At least one of extends, rules, or overrides is required. No other properties are allowed.

description: Acme API style guide
documentationUrl: https://engineering.acme.com/api/governance
extends: [[spectral:oas, recommended]]
formats: [oas3]
rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: error
    given: "$.paths[*]['get','put','post','delete','options','head','patch','trace']"
    then:
      field: summary
      function: truthy

4.2.1. extends

extends inherits from one or more other rulesets. Its value is a string that identifies a ruleset, or an array. Each element of the array is such a string, or an array of the string and a mode. Elements after the mode are ignored.

Elements after the mode are accepted and ignored, so [./base.yaml, all, extra] reads as [./base.yaml, all]. OpenLint accepts them although its own validation schema forbids them for this array. Rejecting them would catch a misplaced bracket.

The string identifies a ruleset as a built-in identifier (spectral:oas, spectral:asyncapi, spectral:arazzo), a file path, an npm module specifier, or a URL. The mode decides which of the inherited rules start out enabled:

Mode Enables
recommended Only rules with recommended: true, which is the default for a rule. The default mode when a bare string is used.
all Every rule in the ruleset.
off No rules. Inherit the definitions without turning any of them on, then enable the ones you want by name.
extends:
  - [spectral:oas, all]
  - [./team-baseline.yaml, "off"]
  - https://example.com/rulesets/security.yaml

With off, a ruleset can extend a large ruleset and enable only the rules it names. § 6.2 Loading a ruleset defines loading, and § 6.4 Rule state: enabled and severity the effect of the modes.

4.2.2. rules

rules maps a rule name to either a Rule Object, which defines a rule or redefines an inherited one, or a shorthand, which adjusts a rule inherited through § 4.2.1 extends. Other rulesets refer to a rule by its name to disable it or change its severity, so renaming a rule breaks them.

A linter also reports problems of its own, such as a syntax error in the document, under fixed codes listed in § 6.8 Results. A rule with one of those names is accepted, but its results cannot be told apart from the linter’s own by their code.

/rec/rules/reserved-namesrulesetruleset-coresince this draft

A rule name SHOULD NOT be one of the codes a linter uses for the results it produces itself: parser, unrecognized-format, and invalid-ref.
4.2.2.1. Shorthands

A value in rules that is not an object is a shorthand: one of the severity names error, warn, info, hint, off, or one of the booleans true and false. It adjusts a rule instead of defining one.

/req/shorthands/inherited-onlyrulesetruleset-coresince this draft

A shorthand in the root rules MUST name a rule the ruleset inherits through extends.
2 tests
/conf/shorthands/inherited A shorthand for a rule inherited through extends is valid. valid
/conf/shorthands/not-inherited A shorthand for a rule the ruleset does not inherit is invalid. invalid

A shorthand in the rules of a file-scoped override names a rule the ruleset defines or inherits. That is checked when the override applies to a document; see /req/overrides/checked-when-applied.

Note: Severities are not written the same way everywhere. A rule’s severity takes a name or a number. A shorthand, in the root rules or in a file-scoped override, takes a name or a boolean but not a number. The rules of a pointer-scoped override take a name or a number but not a boolean.

A numeric shorthand such as r: 0 is rejected only by OpenLint’s validation schema. Its type definitions and its merge code accept a number and would apply it as the severity. Accepting numbers in shorthands would remove the difference with severity without making any ruleset invalid.

extends: spectral:oas
rules:
  operation-tags: error       # raise it
  info-contact: false         # switch it off
  oas3-schema: true           # switch it back on, at its current severity

What each shorthand does is defined in § 6.4 Rule state: enabled and severity.

4.2.3. aliases

An alias names a list of path expressions, so that several rules can share it. A rule references an alias from given with a leading #. Aliases are also under discussion in discussion #2.

An alias name starts with a letter, followed by letters, digits, _, and -. A simple alias is a non-empty array of path expressions.

/req/aliases/no-cyclerulesetruleset-coresince this draft

An alias MUST NOT refer to itself, directly or through other aliases.
2 tests
/conf/aliases/chain An alias defined through another alias, without a cycle, is valid. valid
/conf/aliases/cycle Two aliases that refer to each other are invalid. invalid
aliases:
  Info:
    - $..info
  InfoDescription:
    - "#Info.description"
  PathItemObject:
    - $.paths[*]
  OperationObject:
    - "#PathItemObject['get','put','post','delete','options','head','patch','trace']"

A scoped alias resolves to different expressions depending on the format of the document being linted. One rule can then select, for example, the parameters of both OpenAPI 2 and OpenAPI 3 documents. A scoped alias is an object:

Property Type Required Description
targets array of objects, see Alias target yes One set of path expressions per set of formats.
description string Human-readable purpose of the alias.

Each of its targets is an object:

Property Type Required Description
formats array of strings, see Formats yes The formats this target applies to.
given array of strings, see Path expression list yes The path expressions the alias resolves to for documents of those formats.
aliases:
  ParameterObject:
    description: Every Parameter Object, wherever this spec version keeps them.
    targets:
      - formats: [oas2]
        given:
          - $.parameters[*]
      - formats: [oas3]
        given:
          - $.components.parameters[*]

Aliases are declared at the root of the ruleset or within a file-scoped override. How they expand is defined in § 6.6 Selecting nodes, and which rules an override’s aliases reach in § 6.3 Applying overrides.

4.2.4. overrides

Overrides apply adjustments to a subset of the documents being linted, matched by glob. overrides is a non-empty array of objects, each with a non-empty array files of glob patterns.

files entries are glob patterns, matched as minimatch 3.1.5 [MINIMATCH] matches them, against the path of the document relative to the directory of the ruleset file, with / as the separator. minimatch has no specification of its own, so the glob dialect is what that version accepts.

A glob dialect with a specification of its own, or a written subset of minimatch’s, would let a linter in another language match files without reproducing minimatch.

There are two forms, distinguished by whether every glob in files carries a JSON Pointer [RFC6901] fragment. An override that mixes the two kinds of glob is file-scoped.

An override whose files do not all carry a # fragment is a file-scoped override. It has at least one of extends and rules, and it can also have formats, parserOptions, and aliases.

Note: The schema accepts other properties on a file-scoped override. When the override applies to a document, a property the Ruleset Object does not have makes a linter reject the ruleset, while one it does have, such as description, is accepted; see /req/overrides/checked-when-applied.

overrides:
  - files: ["legacy/**/*.yaml"]
    rules:
      acme-operation-summary: warn
  - files: ["v3/**/*.yaml"]
    formats: [oas3_1]
    extends: [[./strict.yaml, all]]

An override whose files all carry a # fragment is a pointer-scoped override. It has rules, whose every value is a severity name or number, and no other property than files.

overrides:
  - files: ["api.yaml#/paths/~1legacy~1users"]
    rules:
      acme-operation-summary: "off"

A pointer-scoped override can change a rule’s severity at one location in one file, or switch the rule off there. It cannot change what the rule checks. § 6.3 Applying overrides defines how overrides are applied.

4.2.5. parserOptions

Severities for problems found while parsing the document, before any rule runs; see § 5.2 Parsing.

Property Type Description
duplicateKeys number or string, see Severity Severity for duplicate keys found in the document. Defaults to error.
incompatibleValues number or string, see Severity Severity for a YAML mapping key that is not a string scalar. Defaults to error.

No other properties are allowed.

4.2.6. functions and functionsDir

A serialized ruleset names the custom functions it uses in functions, and can name the directory they load from in functionsDir; see § 7.2 Custom functions.

functions:
  - dictionary
functionsDir: ./my-functions   # optional; defaults to "functions"
rules:
  no-evil-words:
    message: "{{error}}"
    given:
      - $.info.title
      - $.info.description
    then:
      function: dictionary

/rec/custom-functions/prefer-corerulesetruleset-coresince this draft

A ruleset SHOULD use the core functions where they express the assertion, and name a custom function only for an assertion they cannot express.

OpenLint runs custom functions without isolation. Whether a conforming linter has to sandbox them, or refuse functions that come from a remote ruleset, is open.

4.3. The Rule Object

A rule selects nodes in a document with given and checks each selected node with then.

Property Type Required Description
given string or array of strings, see Path expression or list yes The part or parts of the document a rule is applied to. A single path expression, or an array of them.
then object or array of objects, see Function invocation or list yes One function invocation, or an array of them, each applied to every node given matches. An empty array asserts nothing.
description string Human-readable statement of what the rule requires.
documentationUrl string (URL) URL explaining the rule and how to satisfy it. Reported alongside results.
message string Template for the reported message. Supports the placeholders {{error}}, {{description}}, {{path}}, {{property}}, and {{value}}.
severity number or string, see Severity Severity of this rule’s results. Defaults to warn.
formats array of strings, see Formats Document formats this rule applies to. Overrides the ruleset-level formats.
recommended boolean Whether the rule is enabled when another ruleset extends this one in recommended mode. Defaults to true.
resolved boolean Whether the rule runs against the document with $refs resolved (true, the default) or against the document as written (false). Set to false for rules about references themselves.
tags array of strings Free-form labels for grouping and filtering rules.
type style or validation The kind of rule. validation rules assert correctness; style rules assert convention.
extensions object Metadata for the author’s own tooling. A linter ignores it.
x-* any Extension property.

No other properties are allowed.

What message and description contribute to a result is defined in § 6.8 Results, how recommended combines with extends in § 6.4 Rule state: enabled and severity, and what resolved changes in § 6.6 Selecting nodes.

Note: tags, type, and extensions carry no behavior in this specification. They exist for tooling built around rulesets.

4.3.1. given

given says where in the document a rule looks. It is a JSONPath expression, such as $.paths[*]['get','put','post','delete','options','head','patch','trace'] for every operation of an OpenAPI description, or a reference to an alias that names one, such as #Operation. JSONPath-Plus has no grammar document, so the dialect is what that implementation accepts; § 6.6 Selecting nodes pins it. then checks each node the expression selects on its own. § 6.6 Selecting nodes defines how the expression is evaluated.

The value of given is a path expression or a non-empty array of them. A path expression that begins with $ is a JSONPath expression, and one that begins with # is an alias reference.

/req/given/alias-definedrulesetruleset-coresince this draft

An alias reference in a rule MUST name an alias declared under aliases in the ruleset whose rules contain the rule or, for a rule in a file-scoped override, in that override or that ruleset. When the rule redefines a rule inherited through extends, the ruleset that defines the inherited rule MUST declare that alias too.
4 tests
/conf/given/alias-declared A rule referencing an alias the ruleset declares is valid. valid
/conf/given/alias-redefined-both A rule that redefines an inherited rule with an alias both rulesets declare is valid. valid
/conf/given/alias-redefined-own A rule that redefines an inherited rule with an alias only the extending ruleset declares is invalid. invalid
/conf/given/alias-undefined A rule referencing an alias the ruleset does not declare is invalid. invalid

A redefined rule keeps resolving its alias references against the ruleset that first defined it, so redefining an inherited rule with an alias reference works only when both rulesets declare the alias, and then uses the inherited ruleset’s. Resolving against the redefining ruleset’s aliases would let an extending ruleset retarget a rule with aliases of its own.

A bare property name such as paths is not a path expression. It begins with neither $ nor #, so the ruleset schema rejects it, and so does a conforming linter.

given:
  - $.info
  - "#OperationObject"

4.3.2. then

then is one function invocation, or an array of them. A linter applies each of them to every node given matched, as § 6.7 Applying functions defines. A function invocation is an object:

Property Type Required Description
function non-empty string yes Name of a core function (alphabetical, casing, defined, enumeration, falsy, length, or, pattern, schema, truthy, undefined, unreferencedReusableObject, xor) or of a custom function declared in functions.
field string Apply the function to part of the matched node: a property name, a path of property names and indices such as info.contact.email or tags[0], or a JSONPath expression starting with $, evaluated against the matched node. @key applies the function to each property key of the matched node.
functionOptions object Options passed to the function. Each function defines the options it accepts.

then: [] is accepted, and the rule then asserts nothing and never reports a result. Rejecting an empty array would catch a rule left unfinished.

/req/then/function-knownrulesetruleset-coresince this draft

function MUST name a core function or a custom function declared in functions.
2 tests
/conf/then/function-core A rule naming a core function is valid. valid
/conf/then/function-unknown A rule naming a function that is neither core nor declared is invalid. invalid

/req/then/function-options-validrulesetruleset-coresince this draft

When function names a core function, functionOptions MUST satisfy that function’s options in § 7.1 Core functions: only the options its table lists, with the types listed, every required option present, and no functionOptions at all for a function that takes no options.
4 tests
/conf/then/options-valid Core functions with options that match their tables are valid. valid
/conf/then/options-on-no-option-function truthy with options is invalid, because it takes none. invalid
/conf/then/options-required-missing length with empty options is invalid, because it needs min or max. invalid
/conf/then/options-unknown-value casing with type: lower, which is not a case the function knows, is invalid. invalid

An unknown property inside an invocation, such as functionOption without the s, is ignored. When the function has no required option it runs with its defaults; when it has one, the ruleset is rejected for the missing functionOptions with a message that does not point at the misspelling. Rejecting the unknown property would catch the misspelling in both cases.

then:
  - field: summary
    function: truthy
  - field: summary
    function: length
    functionOptions:
      max: 120

4.3.3. severity

severity says how serious a result from this rule is, and whether the rule runs at all. A ruleset can write either; results carry the numbers.

Name Number
error 0
warn 1
info 2
hint 3
off -1

A rule’s severity is one of the names or one of the numbers.

What a severity means for a rule, and how it combines with extends, shorthands, and overrides, is defined in § 6.4 Rule state: enabled and severity.

4.3.4. formats

formats limits a rule to documents of certain kinds, so that a rule about OpenAPI paths does not run on an AsyncAPI document linted with the same ruleset.

formats is an array of format identifiers. It can appear on a ruleset, a rule, a file-scoped override, and an alias target.

The identifiers are listed in § 5.4 Formats; which rules run against which documents is defined in § 6.5 Selecting documents.

4.4. Extension properties

/per/extension-properties/anywhererulesetruleset-coresince this draft

A ruleset MAY have extension properties at its root and on any rule, with any value.
2 tests
/conf/extension-properties/accepted A ruleset with extension properties of several types at the root and on a rule is accepted. valid
/conf/parser-options/shape-extension-property An extension property inside parserOptions is invalid: the permission covers the root and rules only, and parserOptions admits no other properties. invalid

Use them to carry ownership, tickets, review dates, or anything else your own tooling needs. Rules also have an extensions object for the same purpose. A linter ignores both. Elsewhere they are not part of the format. The ruleset schema rejects them in parserOptions and in a pointer-scoped override. A file-scoped override, a scoped alias, and an invocation under then accept them, and a linter ignores them there too.

x-owner: platform-team
rules:
  acme-operation-summary:
    x-jira: GOV-142
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

5. Documents

A linter lints documents with the rulesets of § 4 The ruleset format. This section says what a document is to a linter, what happens to it before any rule runs, and how its formats are found.

5.1. The document model

A document is written in JSON [RFC8259] or YAML [YAML-1.2.2]. After parsing, both are the same tree of objects, arrays, strings, numbers, booleans, and null, and that tree is what path expressions select from and what functions see. A linter keeps the mapping from every node back to its position in the source text, because a result reports that position.

Note: Rulesets authored in JavaScript can inline function implementations and whole ruleset objects, which have no JSON representation and are out of scope. Only serialized documents and rulesets are specified.

The root of a document can be any value, including an array, a scalar, or null, which is what an empty file parses to. $ selects it whatever it is.

/req/document/any-rootlinterlinter-coresince this draft

A linter MUST lint a document whose root value is not an object, and $ MUST select that root value.
2 tests
/conf/document/empty-file An empty file is the document null, and a rule on $ sees it. 1 result
/conf/document/root-array A document whose root is an array is linted, and $ selects the array. 1 result

/req/document/encodinglinterlinter-coresince this draft

A linter MUST read a document and a ruleset as UTF-8.
1 test
/conf/document/utf8-keys A document is read as UTF-8, so non-ASCII keys arrive intact in path. 2 results

A UTF-8 byte order mark is accepted, but OpenLint then reports range columns on every line after the first one too far to the left. A UTF-16 document is not rejected; it parses as garbage and produces parser results. Ignoring a byte order mark and rejecting other encodings would make both deterministic.

5.2. Parsing

Problems in a document’s syntax are reported as results with the code parser. A problem that leaves the document with a JSON reading, such as a key that appears twice, still lets the document be linted; parserOptions sets its severity.

/req/parsing/syntax-errorlinterlinter-coresince this draft

When a document has a syntax error, a linter MUST produce a result with the code parser and the severity error.
1 test
/conf/parsing/syntax-error A YAML document with an unclosed flow sequence is reported as a parser error. 1 result

/req/parsing/several-documentslinterlinter-coresince this draft

When a YAML document contains several documents, a linter MUST produce a result with the code parser and the severity error, and MUST lint the first document.
1 test
/conf/parsing/several-documents A YAML file with two documents gets a parser error, and the rules run on the first document. 2 results

Whether rules run against the part of a document before a syntax error is not stated. OpenLint runs them against whatever tree its parser recovers, which for an unterminated flow collection can be nothing.

/req/parser-options/duplicate-keyslinterlinter-coresince this draft

When a document has a duplicate key, a linter MUST produce a result with the code parser and the severity of duplicateKeys, error when it is not set, and MUST NOT produce one when it is off.
3 tests
/conf/parser-options/duplicate-keys-default A duplicate key is reported as an error by default. 1 result
/conf/parser-options/duplicate-keys-warn With duplicateKeys: warn, a duplicate key is reported as a warning. 1 result
/conf/parser-options/duplicate-keys-off With duplicateKeys: off, a duplicate key is not reported. 0 results

/req/parser-options/incompatible-valueslinterlinter-coresince this draft

When a YAML document has a mapping key that is not a string scalar, such as 1, true, or null, a linter MUST produce a result with the code parser and the severity of incompatibleValues, error when it is not set, and MUST NOT produce one when it is off.
1 test
/conf/parser-options/incompatible-values A YAML mapping key that is a number, not a string, has no JSON representation and is reported as an error by default. 1 result

Non-string scalar keys are the one construct OpenLint reports under incompatibleValues. A mapping key that is itself a sequence or a mapping is reported as a warning by the YAML parser, with no option controlling it, and a !!binary or !!set tag ends the run with an error before any result. Whether those constructs belong under incompatibleValues, and whether a tag the linter cannot represent is a parser result, is open.

5.3. Reference resolution

The resolved document is the document after every $ref has been replaced by the value it points to, including references into other files. By default a rule sees the resolved document; a rule that sets resolved: false sees the document as written.

A reference is an object with a $ref property whose value is a URI reference [RFC3986]: a fragment that is a JSON Pointer [RFC6901] into the same document, or a relative or absolute reference to another document, with an optional fragment into it.

/req/resolution/locallinterlinter-coresince this draft

A linter MUST replace a reference whose $ref is a fragment with the value the JSON Pointer names in the same document.
1 test
/conf/resolution/local A $ref to a pointer in the same document is replaced by the referenced schema, whose type the rule then sees. 1 result

/req/resolution/externallinterlinter-coresince this draft

A linter MUST replace a reference whose $ref names another document with the value its fragment names in that document, after resolving the $ref against the location of the referencing document.
1 test
/conf/resolution/external A $ref to another file, relative to the document, is replaced by the referenced schema. 1 result

/req/resolution/cyclelinterlinter-coresince this draft

A linter MUST resolve a document whose references form a cycle without failing, and MUST evaluate rules against it.
1 test
/conf/resolution/cycle A schema that references itself resolves without error, and a rule sees its type. 1 result

/req/resolution/unresolvablelinterlinter-coresince this draft

When a $ref cannot be resolved, a linter MUST produce a result with the code invalid-ref and the severity error, and MUST leave the reference object in place for the rules.
2 tests
/conf/resolution/unresolvable A $ref to a file that does not exist is reported as invalid-ref at severity error. 1 result
/conf/resolution/unresolvable-kept After a $ref fails to resolve, a rule still sees the reference object. 2 results

What a rule sees at the position of a reference in a cycle, and whether resolution follows $ref in every position or only where the document’s format expects one, is not stated.

5.4. Formats

A format names a kind of document, and a linter decides from the document whether it is of that kind, by the condition the table gives. A condition reads the document’s root object; a document whose root is not an object has no format. The string form of a value is the value as JSON writes it, without quotes for a string, so an unquoted 3.0 in YAML is the number 3. A rule scoped to a format runs only against documents of that format, so one ruleset can hold rules for OpenAPI 3.1 and for AsyncAPI 2.6 without either set running on the other kind of document. The table lists the identifiers a ruleset can use, which are the ones the ruleset schema accepts, and how each is detected.

Family Identifier Detected when
OpenAPI oas2 swagger is present and its string form starts with 2 followed by a non-digit or the end.
oas3 openapi is present and its string form starts with 3 followed by a non-digit or the end.
oas3_0 oas3, and the string form of openapi matches ^3\.0(\.[0-9]*)?$.
oas3_1 oas3, and the string form of openapi matches ^3\.1(\.[0-9]*)?$.
oas3_2 oas3, and the string form of openapi matches ^3\.2(\.[0-9]*)?$.
AsyncAPI asyncapi2 The same as aas2.
aas2 asyncapi is present and its string form matches ^2\.(0|[1-9][0-9])\.(0|[1-9][0-9])$.
aas2_0 aas2, and the string form of asyncapi matches ^2\.0(\.[0-9]*)?$.
aas2_1 aas2, and the string form of asyncapi matches ^2\.1(\.[0-9]*)?$.
aas2_2 aas2, and the string form of asyncapi matches ^2\.2(\.[0-9]*)?$.
aas2_3 aas2, and the string form of asyncapi matches ^2\.3(\.[0-9]*)?$.
aas2_4 aas2, and the string form of asyncapi matches ^2\.4(\.[0-9]*)?$.
aas2_5 aas2, and the string form of asyncapi matches ^2\.5(\.[0-9]*)?$.
aas2_6 aas2, and the string form of asyncapi matches ^2\.6(\.[0-9]*)?$.
aas3 asyncapi is present and its string form matches ^3\.(0|[1-9][0-9])\.(0|[1-9][0-9])$.
aas3_0 aas3, and the string form of asyncapi matches ^3\.0(\.[0-9]*)?$.
aas3_1 aas3, and the string form of asyncapi matches ^3\.1(\.[0-9]*)?$.
Arazzo arazzo1_0 arazzo is present and its string form matches ^1\.0(\.[0-9]*)?$.
JSON Schema jsonSchema $schema is a string that contains //json-schema.org/.
jsonSchemaLoose jsonSchema, or type is a JSON Schema type name or an array of them, or enum is an array, or one of allOf, anyOf, oneOf, not, and if is an object or an array.
jsonSchemaDraft4 $schema matches ^https?://json-schema.org/draft-04/(hyper-)?schema#?$.
jsonSchemaDraft6 $schema matches ^https?://json-schema.org/draft-06/(hyper-)?schema#?$.
jsonSchemaDraft7 $schema matches ^https?://json-schema.org/draft-07/(hyper-)?schema#?$.
jsonSchemaDraft2019_09 $schema matches ^https?://json-schema.org/draft/2019-09/(hyper-)?schema#?$.
jsonSchemaDraft2020_12 $schema matches ^https?://json-schema.org/draft/2020-12/(hyper-)?schema#?$.

Any other identifier, such as the typo oas31, makes the ruleset invalid, wherever formats appears. How a rule names formats is defined in § 4.3.4 formats; how a linter detects them and which rules run is defined in § 6.5 Selecting documents.

The set of identifiers is closed, because OpenLint rejects an identifier it does not register. The previous document and schema accepted any string, so that formats added later, or registered by a host application, would stay valid. Which of the two this specification wants is open.

6. Linting

This part defines what a linter does with a ruleset and a document, in the order it happens:

  1. Load the ruleset: check it and load what it extends.

  2. Read the document: parse it, resolve its references, and detect its formats.

  3. Apply the overrides whose globs match the document.

  4. Work out the rule state: which rules are enabled, and at what severity.

  5. Select documents: decide, by format, which rules apply to the document.

  6. Select nodes: evaluate each rule’s given.

  7. Apply functions: run each rule’s then on every node it matched.

  8. Report the results.

6.1. A running example

The sections of this part follow one scenario through the pipeline. A team keeps its shared rules in a base ruleset:

rules:
  operation-summary:
    description: Every operation needs a summary.
    severity: warn
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
  operation-id-camel-case:
    description: operationId is written in camelCase.
    recommended: false
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace'].operationId
    then:
      function: casing
      functionOptions:
        type: camel

A project extends it. It switches on the rule the base leaves off, adds a rule of its own through an alias, scopes its own rules to OpenAPI 3, and raises one severity for the documents under legacy/:

extends: ./acme-base.yaml
formats: [oas3]
aliases:
  Operation:
    - "$.paths[*]['get','put','post','delete']"
rules:
  operation-id-camel-case: true
  operation-has-tags:
    description: Every operation is tagged.
    severity: hint
    given: "#Operation"
    then:
      field: tags
      function: defined
overrides:
  - files: ["legacy/**"]
    rules:
      operation-summary: error

The document being linted is legacy/petstore.yaml. Its get operation has no summary, no tags, and a snake_case operationId; its post operation has all three:

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      operationId: list_pets
      responses:
        "200":
          description: OK
    post:
      summary: Add a pet
      operationId: addPet
      tags: [pets]
      responses:
        "201":
          description: Created

The scenario is also the conformance test /conf/linter/walkthrough, which checks the outcome this part describes.

6.2. Loading a ruleset

Loading turns the ruleset file into the set of rules that will run. A linter first checks the ruleset against § 4 The ruleset format. It then loads the rulesets it extends, recursively, so that their rules are available under their names. Overrides are applied next, per document; see § 6.3 Applying overrides.

In the running example, the linter loads acme-base.yaml and makes operation-summary and operation-id-camel-case available to the project ruleset, which adds operation-has-tags. The document’s path, legacy/petstore.yaml, matches the glob legacy/**, so the override applies to this document and sets operation-summary to error. For a document outside legacy/ the override would be ignored.

/req/linter/reject-invalidlinterlinter-coresince this draft

A linter MUST reject a ruleset that violates any requirement whose conformance target is ruleset, or that extends, directly or through another ruleset’s extends, a ruleset that violates one, and MUST NOT produce results from it.
3 tests
/conf/linter/accept-valid A valid ruleset is loaded and linting produces its results. 2 results
/conf/extends/base-invalid A ruleset that extends a ruleset with an invalid given is rejected. rejected
/conf/linter/reject-invalid Given an invalid ruleset, the linter rejects it and produces no results. rejected

/req/linter/accept-validlinterlinter-coresince this draft

A linter MUST load a ruleset that satisfies every requirement whose conformance target is ruleset, when every ruleset it extends and every custom function it names can be loaded, unless /req/overrides/checked-when-applied has it rejected for the document being linted.
1 test
/conf/linter/accept-valid A valid ruleset is loaded and linting produces its results. 2 results

/req/extends/loadlinterlinter-coresince this draft

A linter MUST load every ruleset identified in extends and make each of its rules available to the extending ruleset under its rule name.
2 tests
/conf/extends/load A rule defined only in the extended ruleset is available to the extending one by name. 2 results
/conf/linter/walkthrough The running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document. 3 results

/req/extends/unresolvablelinterlinter-coresince this draft

A linter MUST reject a ruleset whose extends identifies a ruleset it cannot load.
2 tests
/conf/extends/resolvable A ruleset extending a file that exists is loaded and the inherited rule runs. 1 result
/conf/extends/unresolvable A ruleset extending a file that does not exist is rejected. rejected

How a linter resolves file paths, npm module specifiers, and URLs, and what each is relative to, is implementation-defined and has to be specified. So is whether every conforming linter has to recognize the built-in identifiers, which name rulesets shipped with OpenLint; discussion #6 uses openlint:openapi as the name of a new core OpenAPI ruleset. Resolving an npm module specifier also ties linter-core to the JavaScript package ecosystem, so it could move to a class of its own, as custom functions have.

In OpenLint, a cycle through extends between ruleset files is neither detected nor reported: the loader follows it until the process runs out of memory, so the ruleset never loads. Its core skips a definition it is already loading, but only for definitions held in memory. A conforming linter has to reject cycles, or the ruleset has to be invalid; which is open.

/req/extension-properties/ignoredlinterlinter-coresince this draft

A linter MUST NOT let an extension property change which results it produces.
1 test
/conf/extension-properties/ignored Extension properties at the root and on a rule change nothing about the results. 2 results

6.3. Applying overrides

An override changes the ruleset for the documents its files globs match, so a linter applies overrides per document, after loading and before working out the rule state. A file-scoped override can add rulesets, rules, aliases, and formats; a pointer-scoped override only changes severities at the locations its pointers name.

/req/overrides/scopelinterlinter-coresince this draft

A linter MUST apply an override to a document only when the document’s path, relative to the directory of the ruleset file and with / as the separator, matches one of the override’s files globs.
5 tests
/conf/linter/walkthrough The running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document. 3 results
/conf/overrides/scope-match A document inside the override’s globs gets the override’s severity. 2 results
/conf/overrides/scope-no-match A document outside the override’s globs keeps the base severity. 2 results
/conf/overrides/scope-outside-ruleset-directory The same glob does not match a document in a legacy/ directory that is not next to the ruleset, whatever the working directory. 1 result
/conf/overrides/scope-ruleset-directory A glob is matched against the document’s path relative to the ruleset file’s directory, so legacy/** matches a document in legacy/ next to the ruleset. 1 result

/req/overrides/orderlinterlinter-coresince this draft

When several overrides match a document, a linter MUST apply them in array order, each on top of the previous, and all of them on top of the ruleset itself.
1 test
/conf/overrides/order Of two overrides that both match, the later one wins. 2 results

/req/overrides/extendslinterlinter-coresince this draft

For a document a file-scoped override applies to, a linter MUST load the rulesets the override’s extends identifies and make their rules available in addition to those of the rulesets the ruleset extends, or, when the override has no rules, in place of them.
3 tests
/conf/overrides/extends An override’s extends adds rules for the documents it matches. 1 result
/conf/overrides/extends-with-rules An applying override with extends and rules adds to the rulesets the ruleset extends: a, b, and r all run. 3 results
/conf/overrides/extends-without-rules An applying override with extends and no rules replaces the rulesets the ruleset extends: b and the ruleset’s own r run, a does not. 2 results

An override with extends and no rules drops, for the documents it matches, every rule the ruleset inherits through its own extends, while its own rules stay. With rules, it adds to them. Adding in both cases would make the two forms consistent.

/req/overrides/aliaseslinterlinter-coresince this draft

For a document a file-scoped override applies to, a linter MUST resolve the alias references of each rule the override defines and the ruleset does not against the override’s aliases and the ruleset’s, the override’s alias taking precedence for a name both declare, and MUST resolve the alias references of every other rule as if the override declared no aliases.
3 tests
/conf/overrides/aliases-existing-rule An alias an override declares does not change a rule the ruleset defines: the rule keeps the ruleset’s alias and only its severity changes. 1 result
/conf/overrides/aliases-new-rule A rule an override defines resolves aliases against the override’s, which take precedence over the ruleset’s alias of the same name. 1 result
/conf/overrides/aliases-outside-globs Outside the override’s globs its aliases have no effect, and the rule keeps its default severity. 1 result

An alias declared in an override does not reach the rules the ruleset defines, so overriding an alias by name changes nothing for them. The previous document said an override’s aliases merge by name with the ruleset’s. Applying them to every rule, for the documents the override matches, would make that true.

/req/overrides/reject-foreign-propertylinterlinter-coresince this draft

For a document a file-scoped override applies to, a linter MUST reject the ruleset when the override has a property the Ruleset Object does not have.
2 tests
/conf/overrides/unknown-property-not-applied The same override, applying to no document being linted, does not stop the ruleset from loading. 1 result
/conf/overrides/unknown-property-applied An override with a property the Ruleset Object does not have makes a linter reject the ruleset for a document it applies to. rejected

/req/overrides/reject-unknown-shorthandlinterlinter-coresince this draft

For a document a file-scoped override applies to, a linter MUST reject the ruleset when a shorthand in the override’s rules names a rule the ruleset neither defines nor inherits.
3 tests
/conf/shorthands/override-own-rule A shorthand in an applying override for a rule the ruleset defines itself sets its severity. 1 result
/conf/shorthands/override-unknown-not-applied A shorthand for an unknown rule in an override that applies to no document being linted does not stop the ruleset from loading. 1 result
/conf/shorthands/override-unknown A shorthand in an override for a rule the ruleset neither defines nor inherits makes a linter reject the ruleset for a document the override applies to. rejected

/req/overrides/reject-undeclared-aliaslinterlinter-coresince this draft

For a document a file-scoped override applies to, a linter MUST reject the ruleset when a rule the override redefines has an alias reference to an alias that the ruleset defining the rule does not declare.
2 tests
/conf/overrides/redefined-rule-alias-declared An override that redefines a rule with an alias the ruleset declares is loaded, and the redefined rule runs. 1 result
/conf/overrides/redefined-rule-alias-applied An override that redefines a rule with an alias only the override declares makes a linter reject the ruleset for a document it applies to. rejected

/req/overrides/checked-when-appliedlinterlinter-coresince this draft

A linter MUST NOT reject a ruleset for the reasons in the three requirements above when the override applies to no document being linted.
2 tests
/conf/overrides/unknown-property-not-applied The same override, applying to no document being linted, does not stop the ruleset from loading. 1 result
/conf/shorthands/override-unknown-not-applied A shorthand for an unknown rule in an override that applies to no document being linted does not stop the ruleset from loading. 1 result

These three checks run only when the override applies to a document, as they do in OpenLint. Whether a Rule Object in an override’s rules merges with the rule it redefines, as /req/rules/redefinition-merge says for extends, or replaces it, is not tested. A ruleset with such an error loads without error until the first document the override matches. Everything else in an override is checked when the ruleset loads. Checking these three at load time too would report the error before any document matches.

/req/overrides/pointer-scopelinterlinter-coresince this draft

For a pointer-scoped override, a linter MUST apply the severities only to results whose path is at or below the location the pointer names.
2 tests
/conf/overrides/pointer-scope A pointer-scoped override silences the rule under its pointer only. 1 result
/conf/overrides/pointer-scope-at A pointer that names the result’s own location silences it. 1 result

6.4. Rule state: enabled and severity

Every rule in the loaded ruleset ends up with two things: whether it is enabled, and its effective severity. Four mechanisms set them, in this order: the rule’s own definition, the mode its ruleset was extended with, the shorthands of each ruleset in the chain, and the overrides that match the document.

For the running example and the legacy document, each rule ends up as follows. The project extends the base with a bare string, so the mode is recommended.
Rule Defined as After the mode After the shorthand After the override Enabled Effective severity
operation-summary warn, recommended enabled error yes error
operation-id-camel-case warn by default, not recommended not enabled true: enabled, severity kept yes warn
operation-has-tags hint, own rule yes hint

/req/severity/numeric-equivalencelinterlinter-coresince this draft

A linter MUST treat each numeric severity as equivalent to the name it is paired with in the table in § 4.3.3 severity.
1 test
/conf/severity/numeric-equivalence A rule with severity: 0 reports with the severity of error. 2 results

/req/severity/defaultlinterlinter-coresince this draft

When neither a rule nor any definition it redefines has a severity, a linter MUST use warn.
2 tests
/conf/extends/redefine-keeps-properties A redefinition that sets only given and then keeps the inherited rule’s severity and message. 1 result
/conf/severity/default A rule without severity reports with the severity of warn. 2 results

/req/rules/recommendedlinterlinter-coresince this draft

A linter MUST enable every rule a ruleset defines in its own rules whose recommended is true or absent, and MUST NOT enable one whose recommended is false unless a shorthand in an extending ruleset enables it.
2 tests
/conf/rules/recommended-default A rule the ruleset defines runs whether recommended is true or absent. 2 results
/conf/rules/recommended-false A rule the ruleset defines with recommended: false does not run, while its other rules do. 1 result

/req/extends/mode-alllinterlinter-coresince this draft

When a ruleset is extended with the mode all, a linter MUST enable every rule inherited from it.
2 tests
/conf/extends/mode-all With mode all, both rules run. 2 results
/conf/extends/mode-all-extra-element With [./base.yaml, all, extra], the mode all applies and the extra element is ignored. 1 result

/req/extends/mode-offlinterlinter-coresince this draft

When a ruleset is extended with the mode off, a linter MUST make every rule inherited from it available and MUST NOT enable any of them.
1 test
/conf/extends/mode-off With mode off, no inherited rule runs until it is enabled by name. 1 result

/req/extends/orderlinterlinter-coresince this draft

A linter MUST apply definitions and adjustments of a rule in this order: the rulesets identified by extends, in array order, then the extending ruleset’s own rules.
2 tests
/conf/extends/precedence The extending ruleset’s definition of r wins over both extended rulesets. 1 result
/conf/extends/precedence-order Of two extended rulesets defining the same rule, the later one wins. 1 result

/req/rules/redefinition-mergelinterlinter-coresince this draft

When a Rule Object redefines a rule defined earlier in that order, a linter MUST replace each property the Rule Object has and MUST keep every property it does not have.
1 test
/conf/extends/redefine-keeps-properties A redefinition that sets only given and then keeps the inherited rule’s severity and message. 1 result

A redefinition keeps every property it does not set, so the base’s message or severity survives a redefinition that changes given and then, and can stop matching what the rule now checks. Replacing the whole rule would make a redefinition independent of the rule it replaces.

/req/shorthands/severitylinterlinter-coresince this draft

For a shorthand that is a severity name, a linter MUST set the rule’s effective severity to it and MUST enable the rule, or not enable it when the name is off.
1 test
/conf/shorthands/severity A severity name enables the rule and sets its severity. 1 result

/req/shorthands/falselinterlinter-coresince this draft

For a shorthand that is false, a linter MUST NOT enable the rule and MUST leave its effective severity unchanged.
2 tests
/conf/shorthands/false-control Without the false shorthand, the inherited rule runs. 1 result
/conf/shorthands/false false disables an enabled rule. 0 results

/req/shorthands/truelinterlinter-coresince this draft

For a shorthand that is true, a linter MUST enable the rule unless its effective severity is off, and MUST leave the effective severity unchanged, including a severity set by an earlier ruleset in the chain.
4 tests
/conf/linter/walkthrough The running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document. 3 results
/conf/shorthands/true-enables true enables a rule that is off by default, at the severity it was defined with. 1 result
/conf/shorthands/true-keeps-severity A rule disabled with false in one ruleset and re-enabled with true in the next keeps the severity it was defined with. 1 result
/conf/shorthands/true-off true on a rule whose severity is off does not enable it. 0 results

/req/severity/offlinterlinter-coresince this draft

A linter MUST NOT evaluate a rule that is not enabled, whether its effective severity is off, a shorthand is false, or its recommended is false and nothing enables it, and MUST NOT produce results for it.
5 tests
/conf/rules/recommended-false A rule the ruleset defines with recommended: false does not run, while its other rules do. 1 result
/conf/severity/off-control The same rule at warn produces the results that off suppresses. 2 results
/conf/shorthands/false-control Without the false shorthand, the inherited rule runs. 1 result
/conf/severity/off A rule with severity: off produces no results although it would fail. 0 results
/conf/shorthands/false false disables an enabled rule. 0 results

6.5. Selecting documents

Not every rule applies to every document. A linter detects the formats of the document and keeps the rules that have no formats or whose formats include one of them, so that one ruleset can hold rules for several kinds of document. § 5.4 Formats says what a format is and which identifiers exist.

The example document starts with openapi: 3.1.0, so it is an OpenAPI 3.1 description. The only identifier the rulesets name is oas3, which the project ruleset sets with formats: [oas3] and its own rule operation-has-tags inherits, so the document’s formats are oas3. The base rules have no formats. All three rules apply. Linting an AsyncAPI document with the same ruleset produces one unrecognized-format warning, skips operation-has-tags, and still runs the two base rules.

/req/formats/identifierslinterlinter-coresince this draft

A linter MUST detect a format for a document when the document’s root is an object and the condition the table in § 5.4 Formats gives for the identifier holds, and MUST NOT detect it otherwise, among the identifiers /req/formats/detection-scope lets it detect.
8 tests
/conf/formats/detect-aas2-6 A document with asyncapi: 2.6.0 is aas2_6 and not aas2_5. 1 result
/conf/formats/detect-aas2-needs-patch asyncapi: "2.6" without a patch number is not aas2, while 2.6.0 is. 1 result
/conf/formats/detect-arazzo A document with arazzo: 1.0.1 is arazzo1_0. 1 result
/conf/formats/detect-json-schema-2020-12 A $schema of draft 2020-12 is jsonSchemaDraft2020_12 and not draft 7. 1 result
/conf/formats/detect-json-schema-loose A document with type: object and no $schema is jsonSchemaLoose but not jsonSchema. 1 result
/conf/formats/detect-number-version An unquoted openapi: 3.0 is the number 3, whose string form is 3, so the document is oas3 but not oas3_0. 1 result
/conf/formats/detect-oas2 A document with swagger: "2.0" is oas2 and not oas3. 1 result
/conf/formats/detect-oas3-1 An OpenAPI 3.1 document is detected as oas3_1 and not as oas3_0. 1 result

/req/formats/detection-scopelinterlinter-coresince this draft

A linter MUST detect a document’s formats only among the identifiers named in the formats of the ruleset, of its rules, and of the rulesets it extends and their rules. An identifier named only by a target of a scoped alias is never detected.
1 test
/conf/formats/detection-declared-only oas3_1 is not detected when no rule names it, so for an OpenAPI 3.1 document the alias uses its oas3 target, though an oas3_1 target comes last. 1 result

Because detection is limited to the identifiers a ruleset names, a scoped alias whose target names a format no rule names never uses that target, even for a document of that format. Detecting every identifier in the table would make scoped aliases independent of the rules that use them.

The conditions in the table are the ones OpenLint applies. They read the string form of a version value, so openapi: 3.0 written as a YAML number is OpenAPI 3 but not 3.0, and they accept openapi: 3abc. Conditions on the typed value would be stricter. Which document types the format supports is discussed in discussion #5.

/req/formats/rule-scopelinterlinter-coresince this draft

A linter MUST evaluate a rule that has formats only against documents that have at least one of those formats.
1 test
/conf/formats/rule-scope Of two rules scoped to different formats, only the one whose format the document has runs. 1 result

/req/formats/ruleset-defaultlinterlinter-coresince this draft

A linter MUST treat a rule without formats, defined in a ruleset with formats, as having the formats of the ruleset that defines it, also when another ruleset extends it.
3 tests
/conf/formats/ruleset-default A rule without formats takes the ruleset’s, so it does not run on a document of another format, while a rule scoped to that format does. 1 result
/conf/formats/unrecognized-unscoped A document with none of the ruleset’s formats gets the warning, the ruleset’s own rule does not run, and an unscoped rule from an extended ruleset does. 2 results
/conf/linter/walkthrough The running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document. 3 results

/req/formats/rule-overrides-rulesetlinterlinter-coresince this draft

When both a rule and its ruleset have formats, a linter MUST use the rule’s and ignore the ruleset’s for that rule.
1 test
/conf/formats/rule-overrides-ruleset A rule’s own formats replaces the ruleset’s. 1 result

/req/formats/unscoped-runs-everywherelinterlinter-coresince this draft

A linter MUST evaluate a rule that has no formats, in a ruleset that has none, against every document, whatever its formats.
1 test
/conf/formats/unscoped Without formats anywhere, a rule runs on a document of no known format. 1 result

/req/formats/unrecognized-documentlinterlinter-coresince this draft

When a ruleset has formats, directly or through its rules, and a document has none of them, a linter MUST produce one result with the code unrecognized-format and the severity warn, and MUST still run every rule that has no formats of its own and inherits none from the ruleset.
2 tests
/conf/formats/unrecognized-document A document that has none of the ruleset’s formats gets one warning and no rule results. 1 result
/conf/formats/unrecognized-unscoped A document with none of the ruleset’s formats gets the warning, the ruleset’s own rule does not run, and an unscoped rule from an extended ruleset does. 2 results

6.6. Selecting nodes

For each rule that applies, the linter evaluates every path expression in given against the document and collects the nodes they select, the matched nodes. An alias reference is expanded first. A rule looks through $ref as if the referenced content were inline, unless it sets resolved: false.

In the running example, operation-has-tags has given: "#Operation". The alias expands to $.paths[*]['get','put','post','delete'], which matches two nodes in the document: the get and the post operation under /pets. operation-summary matches the same two nodes through $.paths[*]['get','put','post','delete','options','head','patch','trace'], and operation-id-camel-case matches the two operationId strings.

/req/given/evaluatelinterlinter-coresince this draft

For every rule that is enabled for a document, a linter MUST evaluate each path expression in given against the document and apply the rule’s then to every matched node.
2 tests
/conf/given/evaluate-all-matches Two operations without summary produce one result each. 2 results
/conf/given/evaluate-multiple-expressions Two path expressions in given each produce their results. 2 results

/req/given/no-matchlinterlinter-coresince this draft

A linter MUST NOT produce a result for a rule whose given matches no node in the document.
2 tests
/conf/given/match The same rule on a document where given matches produces results. 1 result
/conf/given/no-match A rule whose given matches nothing produces no results, even though its function would fail on any node. 0 results

/req/given/resolvedlinterlinter-coresince this draft

A linter MUST evaluate given against the resolved document, unless the rule sets resolved to false, in which case it MUST evaluate given against the document as written.
2 tests
/conf/given/resolved-false With resolved: false, given sees the $ref object, which has no type. 1 result
/conf/given/resolved-default By default given sees the resolved document, where the $ref has been replaced by a schema with type. 0 results

/req/given/jsonpathlinterlinter-coresince this draft

A linter MUST evaluate a JSONPath expression as JSONPath-Plus 10.4.0 [JSONPATH-PLUS] evaluates it, including its filter expressions in parentheses, such as $..parameters[?(@.in === "header")].
1 test
/conf/given/jsonpath-filter A filter expression in parentheses, ?(@.in === "header"), selects the header parameter with an empty name; the index in its path is a string. 1 result

JSONPath-Plus has no specification of its own, and its filter expressions are JavaScript. OpenLint evaluates most expressions with Nimma and falls back to JSONPath-Plus for the rest; a filter in the syntax of JSONPath [RFC9535], ?@.in == 'header', matches nothing in it. Making RFC 9535 normative would give the dialect a specification, at the cost of the rulesets that use JavaScript filters.

/req/given/once-per-nodelinterlinter-coresince this draft

A linter MUST apply a rule’s then to a node once, even when several of the rule’s path expressions match it.
1 test
/conf/given/once-per-node A node matched by three path expressions of one rule is checked once. 1 result

/req/given/alias-expansionlinterlinter-coresince this draft

A linter MUST evaluate an alias reference as the set of the named alias’s path expressions, each with the remainder of the reference after the alias name appended to it.
2 tests
/conf/given/alias-expansion #Info.description is evaluated as $..info.description. 1 result
/conf/linter/walkthrough The running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document. 3 results
With aliases: { Info: ["$..info"] }, the alias reference #Info.description is evaluated as $..info.description.

/req/aliases/composelinterlinter-coresince this draft

When an alias’s path expressions contain alias references, a linter MUST expand those references in turn, until no reference remains.
1 test
/conf/aliases/compose An alias defined through another alias expands to the full path. 1 result

/req/aliases/scopedlinterlinter-coresince this draft

For a scoped alias, a linter MUST use the given of the last target whose formats include a format of the document, and MUST match no node with it when no target does.
4 tests
/conf/aliases/scoped-last-target For an OpenAPI 3.1 document, which has both oas3 and oas3_1, only the last matching target is used. 1 result
/conf/aliases/scoped-no-target A scoped alias with no target for the document’s formats matches nothing; only the format warning is reported. 1 result
/conf/aliases/scoped-oas2 On an OpenAPI 2 document, the scoped alias uses the oas2 target. 1 result
/conf/aliases/scoped-oas3 On an OpenAPI 3 document, the scoped alias uses the oas3 target. 1 result

Note: Aliases are resolved against the ruleset that defines the rule, not the ruleset that extends it. A rule inherited through extends keeps the aliases of the ruleset it came from.

Only the last matching target counts, so a document with two formats, such as oas3 and oas3_1, uses one target even when two match. Using every matching target, or the first, would read more naturally as a list.

6.7. Applying functions

A function is applied to one value: the matched node, or each value field selects in it, or each of its keys for @key. It receives that value and the invocation’s functionOptions, and it returns the problems it found, each with a message. Every problem becomes a result of the rule. A function that returns no problems passes. § 7.1 Core functions specifies each core function by what it asserts. A custom function has only this contract; see § 7.2 Custom functions.

For the get operation of the running example, operation-summary applies truthy to the summary property, which is absent, so the function returns one problem. operation-has-tags applies defined to tags, which is also absent, and gets one problem. operation-id-camel-case applies casing with type: camel to list_pets, which is not in camel case, and gets one problem. For the post operation all three functions pass. The three problems become three results.

/req/then/apply-eachlinterlinter-coresince this draft

For each matched node, a linter MUST apply every function invocation in then, and MUST report each problem a function returns as a result.
2 tests
/conf/then/apply-each Two invocations that both fail on the same node produce two results. 2 results
/conf/then/apply-each-problem One invocation whose function returns two problems produces two results. 2 results

/req/then/fieldlinterlinter-coresince this draft

When an invocation has field, a linter MUST apply the function to each value field selects in the matched node: the property it names, the value at the path of property names and indices it names, or every value its JSONPath expression selects when it starts with $. When field selects nothing, the linter MUST apply the function once, to no value, and a result MUST have the path and range of the matched node. A result for a selected value MUST have the path of that value.
4 tests
/conf/then/field With field, the function sees the named property of the matched node. 1 result
/conf/then/field-jsonpath A field that is a JSONPath expression applies the function to every value it selects. 2 results
/conf/then/field-missing With field naming a property the node lacks, the function sees no value, so defined fails. 1 result
/conf/then/field-path A field that is a path of property names reaches a nested value. 1 result

/req/then/field-keylinterlinter-coresince this draft

When field is @key, a linter MUST apply the function once to each property key of the matched node, and a result for a key MUST have the path of that property.
1 test
/conf/then/field-key With field: "@key" on a matched object, the function sees each of its property keys; the key that does not match the pattern is reported. 1 result

Note: @key goes with a given that selects the parent. With given: $.paths and field: "@key", the function checks every path. With given: $.paths[*], it would check the keys inside each path item.

/req/then/function-optionslinterlinter-coresince this draft

A linter MUST pass the invocation’s functionOptions to the function.
1 test
/conf/then/function-options The max option reaches the length function. 1 result

6.8. Results

A linter reports what it found as results. This section defines the result model that discussion #13 asked for.

Field Type Description
code string The rule name, or one of the linter’s own codes below.
path array of strings The keys and indices from the document root to the value the result is about. An index is written as a string, such as "0". An empty array stands for the document as a whole, as for unrecognized-format.
severity integer The numeric effective severity: 0 to 3.
message string The message, after placeholder substitution.
range object start and end, each with a zero-based line and character, locating the value in the source text. Characters are counted in UTF-16 code units, and end is just after the value.
source string The location of the document, when it has one: a file path or URL.
documentationUrl string Where to read about the rule; see /rec/result/documentation-url.

/req/result/fieldslinterlinter-coresince this draft

Every result a linter produces MUST have code, path, severity, message, and range.
1 test
/conf/result/fields Every result has the five required fields. 2 results

/req/result/codelinterlinter-coresince this draft

The code of a result a rule produces MUST be its rule name. The code of a result the linter produces itself MUST be one of those in the table below.
1 test
/conf/result/code The code of a result is the rule name. 2 results
Code Reported for
parser A problem in the document’s syntax; see § 5.2 Parsing.
unrecognized-format A document that has none of the ruleset’s formats; see § 6.5 Selecting documents.
invalid-ref A $ref that cannot be resolved; see § 5.3 Reference resolution.

/req/result/severitylinterlinter-coresince this draft

The severity of a result MUST be the numeric form of the rule’s effective severity.
3 tests
/conf/linter/walkthrough The running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document. 3 results
/conf/result/severity A rule with severity: hint reports severity: 3. 2 results
/conf/severity/result A rule inherited at warn and raised to error reports with the severity of error. 2 results

/req/result/rangelinterlinter-coresince this draft

The range of a result MUST locate the value the result is about in the source text of the document: start at its first character and end just after its last, each with a zero-based line and a zero-based character counted in UTF-16 code units.
2 tests
/conf/result/range A result about the root value starts at line 0, character 0. 1 result
/conf/result/range-utf16 A character before the value that needs two UTF-16 code units counts as two, and the end is just after the value. 1 result

/req/result/sourcelinterlinter-coresince this draft

When the document was read from a file path or URL, every result for it MUST carry that location in source, as an absolute path or the URL.
1 test
/conf/result/source A result for a document read from a file carries that file’s path in source. 2 results

/req/result/orderlinterlinter-coresince this draft

A linter MUST list results in the order of the documents they belong to, as the documents were given, and within a document by the line and then the character of range.start, and then by code.
1 test
/conf/result/order Results come in order of position, and by code at the same position, whatever the order of the rules. 5 results

/req/result/reference-targetlinterlinter-coresince this draft

When a rule ran against the resolved document and the value a result is about was reached through a reference, the result MUST carry the path, range, and source of the value where it is written, and a value reached through several references MUST produce one result.
1 test
/conf/resolution/shared-reference A schema referenced from two operations fails a rule once, at the place where the schema is written. 1 result

/rec/result/documentation-urllinterlinter-coresince this draft

When the rule that produced a result has a documentationUrl, a linter SHOULD carry it in the result’s documentationUrl, and when the rule has none but the ruleset that defines it has one, that URL followed by # and the rule name.

Indices in path are strings, ["names", "1"], as OpenLint reports them. Numbers would tell an index apart from a key that happens to be numeric.

6.8.1. The message

message takes five placeholders:

Placeholder Resolves to
{{error}} The message the function produced.
{{description}} The rule’s description.
{{path}} The full path to the offending value.
{{property}} The key or index of the offending value.
{{value}} The offending value itself.

/req/message/overridelinterlinter-coresince this draft

When a rule has message, the message of every result it produces MUST be the rule’s message with each placeholder substituted.
1 test
/conf/message/override A rule’s message replaces the reported message. 2 results

/req/message/placeholderslinterlinter-coresince this draft

A linter MUST substitute {{error}} with the message the function produced, {{description}} with the rule’s description, {{path}} with the path to the offending value as a JSON Pointer [RFC6901] with a leading #, {{property}} with the last key or index of that path, and {{value}} with the offending value.
3 tests
/conf/message/placeholder-description {{description}} resolves to the rule’s description. 2 results
/conf/message/placeholder-path {{path}} is the JSON Pointer to the offending value, with a leading #. 1 result
/conf/message/placeholder-property {{property}} resolves to the key of the offending value. 1 result

{{value}} substitutes a string as it is and an object as [object Object], which no ruleset wants. A JSON serialization of the value would be useful, and is a change in output.

/req/message/defaultlinterlinter-coresince this draft

When a rule has no message, the message of every result it produces MUST be the rule’s description, or the message the function produced when the rule has no description.
1 test
/conf/message/default-description Without message, the result carries the rule’s description. 2 results
message: "{{description}}: `{{property}}` was {{value}}"
The results of the running example in JSON, with source shortened. Severities are the effective ones from § 6.4 Rule state: enabled and severity, and every result points into the get operation.
[
  {
    "code": "operation-has-tags",
    "path": ["paths", "/pets", "get"],
    "message": "Every operation is tagged.",
    "severity": 3,
    "range": {
      "start": { "line": 6, "character": 8 },
      "end": { "line": 10, "character": 25 }
    },
    "source": "legacy/petstore.yaml"
  },
  {
    "code": "operation-summary",
    "path": ["paths", "/pets", "get"],
    "message": "Every operation needs a summary.",
    "severity": 0,
    "range": {
      "start": { "line": 6, "character": 8 },
      "end": { "line": 10, "character": 25 }
    },
    "source": "legacy/petstore.yaml"
  },
  {
    "code": "operation-id-camel-case",
    "path": ["paths", "/pets", "get", "operationId"],
    "message": "operationId is written in camelCase.",
    "severity": 1,
    "range": {
      "start": { "line": 7, "character": 19 },
      "end": { "line": 7, "character": 28 }
    },
    "source": "legacy/petstore.yaml"
  }
]

7. Functions

A rule names a function in then. It is either a core function, which every conforming linter provides, or a custom function, which a ruleset supplies as code. § 6.7 Applying functions defines how a function is applied.

7.1. Core functions

A conforming linter provides these functions under these names. Each function has a purpose, the values it applies to, its options, and one requirement that states what it asserts. The options tables are normative through /req/then/function-options-valid.

/req/functions/input-typeslinterlinter-coresince this draft

A core function MUST pass when the value it is applied to is absent or of a type the function does not apply to, unless its requirement below says otherwise.
1 test
/conf/functions/input-types pattern passes on a number and on an absent value, and fails only on the string that does not match; xor passes on a string. 1 result

7.1.1. truthy

The value is present and is not false, "", 0, or null. Use it for required fields such as a summary or a contact.

It applies to any value, and to an absent one.

This function takes no options.

/req/functions/truthylinterlinter-coresince this draft

truthy MUST fail when the value is absent, false, "", 0, or null, and MUST pass otherwise, including for an empty array and an empty object.
1 test
/conf/functions/truthy truthy fails on false, "", 0, and null, and passes on an empty array, an empty object, and a string. 4 results
operation-summary:
  given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
  then:
    field: summary
    function: truthy

7.1.2. falsy

The value is absent, false, "", 0, or null, so falsy passes where truthy fails. Use it to forbid a field, or to require that a flag is off.

It applies to any value, and to an absent one.

This function takes no options.

/req/functions/falsylinterlinter-coresince this draft

falsy MUST fail when the value is present and not one of false, "", 0, or null, and MUST pass otherwise.
1 test
/conf/functions/falsy falsy fails on an empty array, an empty object, and a string, and passes on false, "", 0, and null. 3 results

Note: truthy and falsy follow ECMAScript truthiness [ECMA-262-2025]. Their requirements list the values in full because languages differ on whether an empty array or object is truthy.

no-deprecated-operations:
  given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
  then:
    field: deprecated
    function: falsy

7.1.3. defined

The value is present, whatever it is. Unlike truthy, an empty string or false passes.

It applies to any value, and to an absent one.

This function takes no options.

/req/functions/definedlinterlinter-coresince this draft

defined MUST fail when the value is absent and MUST pass when it is present, whatever it is.
1 test
/conf/functions/defined defined fails on an absent property and passes on a present one. 1 result
servers-declared:
  given: $
  then:
    field: servers
    function: defined

7.1.4. undefined

The value is absent. Use it to forbid a property.

It applies to any value, and to an absent one.

This function takes no options.

/req/functions/undefinedlinterlinter-coresince this draft

undefined MUST fail when the value is present and MUST pass when it is absent.
1 test
/conf/functions/undefined undefined fails on a present property and passes on an absent one. 1 result
no-host:
  given: $
  then:
    field: host
    function: undefined

7.1.5. pattern

The value is a string that matches, or does not match, a regular expression.

It applies to strings.

Option Type Required Default Meaning
match string one of the two A regular expression the value has to match.
notMatch string one of the two A regular expression; the value fails when it matches it.

A regular expression is written bare, ^[a-z-]+$, or between slashes with flags, /^[a-z-]+$/i, with the flags of ECMAScript.

/req/functions/patternlinterlinter-coresince this draft

pattern MUST fail when the option match is given and the value does not match it, or when the option notMatch is given and the value matches it. Both options are regular expressions as defined by ECMAScript [ECMA-262-2025].
1 test
/conf/functions/pattern match fails on a string that does not match, notMatch fails on one that does. 2 results

pattern takes the regular expressions and flags of ECMAScript, so a linter written in another language has to reproduce ECMAScript’s semantics. I-Regexp [RFC9485] is a dialect meant to behave the same in every language, but it has no lookaround or backreferences, so rulesets that use them would break. Which dialect a later version adopts is open.

paths-kebab-case:
  given: $.paths
  then:
    field: "@key"
    function: pattern
    functionOptions:
      match: "^/[a-z-]+(/[a-z-{}]+)*$"

7.1.6. casing

The value is a string written in a named case convention.

It applies to non-empty strings.

Option Type Required Default Meaning
type string yes One of flat, camel, pascal, kebab, cobol, snake, macro.
disallowDigits boolean false When true, digits are not allowed anywhere in the value.
separator object char, a single character that separates groups of words and is required in the object, and allowLeading, whether the value can start with it.

/req/functions/casinglinterlinter-coresince this draft

casing MUST fail when the value is a non-empty string that is not written in the case named by the option type: one of flat, camel, pascal, kebab, cobol, snake, and macro, where only ASCII letters and digits count as written in a case.
3 tests
/conf/functions/casing casing with type: kebab fails on a camel-case string. 1 result
/conf/functions/casing-non-ascii Only ASCII letters count, so éclair is not camel case; the empty string passes. 1 result
/conf/functions/casing-types Each case accepts a value written in it and rejects one written in another case. 6 results

The exact character classes of each case have to be stated before this requirement is complete.

operation-id-camel-case:
  given: $.paths[*]['get','put','post','delete','options','head','patch','trace'].operationId
  then:
    function: casing
    functionOptions:
      type: camel

7.1.7. length

The size of the value is within bounds: the number of characters of a string, of elements of an array, of properties of an object, or the value of a number.

It applies to strings, arrays, objects, and numbers.

Option Type Required Default Meaning
min number one of the two The smallest length that passes.
max number one of the two The largest length that passes.

/req/functions/lengthlinterlinter-coresince this draft

length MUST fail when the length of the value is below the option min or above the option max, where the length of a string is its number of UTF-16 code units, of an array its number of elements, of an object its number of properties, and of a number the number itself.
3 tests
/conf/functions/length length with max: 4 fails on a string of six characters and an array of five elements, and passes on a number below the bound. 2 results
/conf/functions/length-code-units A string’s length is its number of UTF-16 code units: é is one, an emoji two. 1 result
/conf/functions/length-min length with min: 2 fails on an object with one property and a string of one character, and passes on an object with two. 2 results
short-summary:
  given: $.paths[*]['get','put','post','delete','options','head','patch','trace'].summary
  then:
    function: length
    functionOptions:
      max: 120

7.1.8. alphabetical

The elements of an array, or the keys of an object, are in ascending order.

It applies to arrays and objects.

Option Type Required Default Meaning
keyedBy string For an array of objects, the property to compare the elements by.

/req/functions/alphabeticallinterlinter-coresince this draft

alphabetical MUST fail when the elements of an array, or the keys of an object, are not in ascending order, where numbers are compared by value, strings as localeCompare of ECMAScript [ECMA-402] compares them with no locale given, and two values of different types count as in order. With the option keyedBy, the elements of an array of objects are compared by that property.
4 tests
/conf/functions/alphabetical alphabetical fails on an unsorted array and passes on a sorted one. 1 result
/conf/functions/alphabetical-case Strings are compared with localeCompare, so a comes before B and B after a. 1 result
/conf/functions/alphabetical-keyed-by With keyedBy, an array of objects is compared by that property. 1 result
/conf/functions/alphabetical-numbers Numbers are compared by value, so [2, 10] is in order and [10, 9] is not. 1 result

localeCompare without a locale uses the locale of the environment, so the order of strings such as ä and z can differ between machines. A fixed collation, such as code point order, would make the function portable at the cost of changing the order of mixed-case names.

tags-alphabetical:
  given: $.tags
  then:
    function: alphabetical
    functionOptions:
      keyedBy: name

7.1.9. enumeration

The value is one of a fixed set.

It applies to strings, numbers, booleans, and null.

Option Type Required Default Meaning
values array of string, number, boolean, or null yes The values that pass.

/req/functions/enumerationlinterlinter-coresince this draft

enumeration MUST fail when the value is not one of the elements of the option values, compared by JSON type and value, so that "1" is not 1 while 1.0 is.
2 tests
/conf/functions/enumeration enumeration fails on a value outside values. 1 result
/conf/functions/enumeration-types "1" is not one of [1], while 1.0 is. 1 result
audience-known:
  given: $.info.x-audience
  then:
    function: enumeration
    functionOptions:
      values: [internal, partner, public]

7.1.10. schema

The value validates against a JSON Schema. Use it when the shape to assert is more than one property deep.

It applies to any present value, and fails on an absent one.

Option Type Required Default Meaning
schema object yes The JSON Schema to validate against.
dialect string auto The draft to read the schema in: one of draft4, draft6, draft7, draft2019-09, draft2020-12, or auto, which takes it from $schema and falls back to draft7.
allErrors boolean false Report every validation error as a problem, not only the first.

The drafts dialect accepts, and the specification of each:

Dialect Specification
draft4 [JSON-SCHEMA-DRAFT-04]
draft6 [JSON-SCHEMA-DRAFT-06]
draft7 [JSON-SCHEMA-DRAFT-07]
draft2019-09 [JSON-SCHEMA-DRAFT-2019-09]
draft2020-12 [JSON-SCHEMA-CORE-2020-12] and [JSON-SCHEMA-VALIDATION-2020-12]

/req/functions/schemalinterlinter-coresince this draft

schema MUST fail when the value is absent, or when it does not validate against the JSON Schema given as the option schema, asserting the format keyword and resolving a $ref within the option against the option itself, with one problem for the first failed keyword, or one per failed keyword when the option allErrors is true. The schema is read in the draft the option dialect names, as the table above defines each draft; with dialect absent or auto, in the draft its $schema names, and in draft 7 when it names none.
8 tests
/conf/functions/schema schema fails on a value that does not validate against the given JSON Schema. 1 result
/conf/functions/schema-absent schema fails on an absent value. 1 result
/conf/functions/schema-all-errors With allErrors: true, a value that fails two keywords gets two problems. 2 results
/conf/functions/schema-default-draft Without $schema or dialect, the schema is read as draft 7: prefixItems is ignored, required is not. 1 result
/conf/functions/schema-dialect With dialect: draft2020-12, prefixItems applies. 1 result
/conf/functions/schema-first-error Without allErrors, a value that fails two keywords gets one problem. 1 result
/conf/functions/schema-format The format keyword is asserted, so not-an-email fails format: email. 1 result
/conf/functions/schema-ref A $ref inside the option resolves against the option’s own $defs. 1 result
contact-shape:
  given: $.info.contact
  then:
    function: schema
    functionOptions:
      schema:
        type: object
        required: [name, email]
        properties:
          email:
            type: string
            format: email

7.1.11. xor

Exactly one of a set of properties is present.

It applies to objects.

Option Type Required Default Meaning
properties array of at least two strings yes The property names, of which exactly one has to be present.

/req/functions/xorlinterlinter-coresince this draft

xor MUST fail unless exactly one of the properties named by the option properties is present on the value, where a property is present when the object has the key, whatever its value.
2 tests
/conf/functions/xor xor fails unless exactly one of properties is present. 2 results
/conf/functions/xor-null A key whose value is null counts as present, so {a: null, b: 1} has two of the properties. 1 result
example-or-examples:
  given: $.paths[*]['get','put','post','delete','options','head','patch','trace'].responses[*].content[*]
  then:
    function: xor
    functionOptions:
      properties: [example, examples]

7.1.12. or

At least one of a set of properties is present.

It applies to objects.

Option Type Required Default Meaning
properties array of at least two strings yes The property names, of which at least one has to be present.

/req/functions/orlinterlinter-coresince this draft

or MUST fail unless at least one of the properties named by the option properties is present on the value, where a property is present when the object has the key, whatever its value.
2 tests
/conf/functions/or or fails unless at least one of properties is present. 1 result
/conf/functions/or-null A key whose value is null counts as present, so {a: null} satisfies or and {} does not. 1 result
operation-documented:
  given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
  then:
    function: or
    functionOptions:
      properties: [summary, description]

7.1.13. unreferencedReusableObject

Every member of a container of reusable definitions is referenced somewhere. Use it to find unused schemas, parameters, or responses.

It applies to objects.

Option Type Required Default Meaning
reusableObjectsLocation string yes A JSON Pointer fragment to the container, such as #/components/schemas or #/definitions.

/req/functions/unreferenced-reusable-objectlinterlinter-coresince this draft

unreferencedReusableObject, applied to an object of reusable definitions, MUST fail once for each member that no $ref in the documents being linted points to, where the option reusableObjectsLocation is the JSON Pointer [RFC6901] of that object in the document.
1 test
/conf/functions/unreferenced-reusable-object unreferencedReusableObject reports a member of the container that nothing references. 1 result

Note: The function counts references, which no longer exist in the resolved document, so the rule in the example sets resolved: false. Members referenced only by documents outside the lint run count as unreferenced.

no-unused-schemas:
  resolved: false
  given: $.components.schemas
  then:
    function: unreferencedReusableObject
    functionOptions:
      reusableObjectsLocation: "#/components/schemas"

7.2. Custom functions

A ruleset declares the custom functions it uses as described in § 4.2.6 functions and functionsDir.

Custom functions are specified in two parts. The contract, the class /conf/linter-custom-functions, says how a linter resolves a function a ruleset names, calls it, and reports what it returns, whatever language the function is written in. A binding says how a function is written and found in one language. This version defines one binding, for JavaScript, the class /conf/linter-custom-functions-javascript. A linter claims the contract together with a binding, and can decline both.

A linter written in Go needs a Go binding, and this version defines none. vacuum, a linter written in Go, loads custom functions as Go plugins from a directory named on its command line, registered under the name each function reports, and does not read functions or functionsDir. A Go binding would have to say where a linter finds a function, which symbol it loads, and the function’s signature in types this specification owns. Go plugins also load only into a binary built with the same Go version and package versions, so a plugin built for one linter does not load into another.

A binding ties a function to one language, so a ruleset whose functions are written in that language runs only on linters that bind it. A binding to WebAssembly, or to a function that runs as a separate process, would let any linter run functions written in any language that targets it. Whether a later version defines such a binding is open.

7.2.1. The contract

/req/custom-functions/missinglinterlinter-custom-functionssince this draft

A linter MUST reject a ruleset that names a function it cannot load.
2 tests
/conf/custom-functions/present A ruleset naming a function whose file exists is loaded and the function runs. 1 result
/conf/custom-functions/missing A ruleset naming a function whose file does not exist is rejected. rejected

/req/custom-functions/invokelinterlinter-custom-functionssince this draft

A linter MUST call a custom function with the value the invocation targets and the invocation’s functionOptions, and MUST report each problem the function returns as a result whose message is the problem’s message.
1 test
/conf/custom-functions/invoke The function receives the value and the options, and each problem it returns becomes a result with its message. 2 results

/req/custom-functions/problem-pathlinterlinter-custom-functionssince this draft

A linter MUST report a problem that carries a path at that path in the document, a problem without one at the matched node, and a problem whose path is not in the document at the document’s root.
1 test
/conf/custom-functions/problem-path A problem with a path is reported there; one whose path is not in the document is reported at the root. 2 results

/req/custom-functions/no-problemslinterlinter-custom-functionssince this draft

A linter MUST treat a custom function that returns nothing as returning no problems.
1 test
/conf/custom-functions/returns-nothing A function that returns nothing reports no problems, while one that returns a problem does. 1 result

A function that throws, or returns something that is not a list of problems, ends the run with no results and an error. Reporting it as a result for the rule, with the error as its message, would keep the results of the other rules.

Note: The contract is tested through the JavaScript binding, the only binding this version defines, so the fixtures of its tests are JavaScript functions.

7.2.2. JavaScript binding

/req/custom-functions/javascript-loadlinterlinter-custom-functions-javascriptsince this draft

A linter MUST load each name in functions from <functionsDir>/<name>.js, resolved relative to the ruleset file, with functions as the directory when functionsDir is absent.
2 tests
/conf/custom-functions/load-custom-dir With functionsDir, the function is loaded from that directory. 1 result
/conf/custom-functions/load-default-dir A function named in functions is loaded from functions/<name>.js next to the ruleset. 1 result

/req/custom-functions/javascript-promiselinterlinter-custom-functions-javascriptsince this draft

When a JavaScript custom function returns a promise, a linter MUST wait for it and use the problems it resolves to.
1 test
/conf/custom-functions/javascript-promise A function that returns a promise is awaited, and the problems it resolves to are reported. 1 result

A custom function is a JavaScript module whose export is the function. It receives the value and the options, and returns an array of problems, each an object with a message; an empty array means the value passes.

// functions/forbiddenWords.js
module.exports = (value, options) => {
  const problems = [];
  for (const word of options.forbidden) {
    if (String(value).includes(word)) {
      problems.push({ message: `Contains "${word}".` });
    }
  }
  return problems;
};

A function module in CommonJS and one in ES module syntax both load in OpenLint, and the function receives a third argument with the document, the rule, and the path. Neither is stated normatively.

8. Security and privacy considerations

A ruleset is configuration, but loading one can read files, fetch URLs, and run code. Anyone who lints with a ruleset they did not write, or extends one, trusts its author with all of that. This section collects what a linter and its users have to be aware of. It states no requirements.

Custom functions run code. A ruleset that names custom functions depends on code, and its document alone does not say what those rules check. This specification does not require a linter to isolate that code, so extending an untrusted ruleset that has custom functions runs that code with the linter’s privileges, and functionsDir resolved relative to a remote ruleset loads code from that remote location.

extends fetches rulesets. An extends entry that is a URL makes the linter fetch it, and an npm module specifier makes it load a package. Nothing in the format pins what is fetched, so a ruleset fetched twice can differ, and a compromised host or package changes what every document is checked against. A ruleset that is extended by file path is read from wherever the path leads.

Path expressions and regular expressions evaluate input. A JSONPath-Plus filter expression in given is JavaScript evaluated by the linter, and a pattern option is an ECMAScript regular expression, which can take exponential time on crafted input. Both come from the ruleset, so a ruleset is as trusted as code even without custom functions.

References read other documents. Resolving a $ref to an external file or URL reads it, so linting an untrusted document can make the linter read any file it has access to and fetch from any host it can reach. The schema function’s option can carry $ref too.

Results carry locations. source is the location of the document, which can be an absolute path or a URL, and results are often written to logs and CI output that others can read.

Appendix A: Requirements index

4 conformance classes, 93 requirements, 3 recommendations, 1 permission, and 238 conformance tests. The same data is available as requirements.json, and the tests with their fixtures under conformance/.

/conf/ruleset-core

The ruleset format: what a ruleset document has to look like to be loaded by any conforming linter. Most of it is one requirement, validity against the ruleset schema. The other requirements are constraints a schema cannot express. Target: ruleset Depends on: none.

Identifier Section Tests Since
/req/ruleset/document The ruleset document /conf/ruleset/document-json, /conf/ruleset/document-object, /conf/ruleset/document-syntax-error this draft
/req/ruleset/single-documentproposed The ruleset document /conf/ruleset/document-single-yaml-document, /conf/ruleset/document-two-yaml-documents this draft
/req/ruleset/schema The ruleset document 66 tests, grouped by section in Appendix B: Conformance tests this draft
/rec/rules/reserved-names rules Not tested: a recommendation. this draft
/req/shorthands/inherited-only Shorthands /conf/shorthands/inherited, /conf/shorthands/not-inherited this draft
/req/aliases/no-cycle aliases /conf/aliases/chain, /conf/aliases/cycle this draft
/rec/custom-functions/prefer-core functions and functionsDir Not tested: a recommendation. this draft
/req/given/alias-defined given /conf/given/alias-declared, /conf/given/alias-redefined-both, /conf/given/alias-redefined-own, /conf/given/alias-undefined this draft
/req/then/function-known then /conf/then/function-core, /conf/then/function-unknown this draft
/req/then/function-options-valid then /conf/then/options-valid, /conf/then/options-on-no-option-function, /conf/then/options-required-missing, /conf/then/options-unknown-value this draft
/per/extension-properties/anywhere Extension properties /conf/extension-properties/accepted, /conf/parser-options/shape-extension-property this draft

/conf/linter-core

Everything a linter does with a ruleset and a document: loading, parsing and resolution, rule state, selecting documents and nodes, applying functions, and the results and messages it reports. It depends on ruleset-core because a linter rejects a ruleset that does not conform to that class. #loading says how. Target: linter Depends on: ruleset-core.

Identifier Section Tests Since
/req/document/any-root The document model /conf/document/empty-file, /conf/document/root-array this draft
/req/document/encoding The document model /conf/document/utf8-keys this draft
/req/parsing/syntax-error Parsing /conf/parsing/syntax-error this draft
/req/parsing/several-documents Parsing /conf/parsing/several-documents this draft
/req/parser-options/duplicate-keys Parsing /conf/parser-options/duplicate-keys-default, /conf/parser-options/duplicate-keys-warn, /conf/parser-options/duplicate-keys-off this draft
/req/parser-options/incompatible-values Parsing /conf/parser-options/incompatible-values this draft
/req/resolution/local Reference resolution /conf/resolution/local this draft
/req/resolution/external Reference resolution /conf/resolution/external this draft
/req/resolution/cycle Reference resolution /conf/resolution/cycle this draft
/req/resolution/unresolvable Reference resolution /conf/resolution/unresolvable, /conf/resolution/unresolvable-kept this draft
/req/linter/reject-invalid Loading a ruleset /conf/linter/accept-valid, /conf/extends/base-invalid, /conf/linter/reject-invalid this draft
/req/linter/accept-valid Loading a ruleset /conf/linter/accept-valid this draft
/req/extends/load Loading a ruleset /conf/extends/load, /conf/linter/walkthrough this draft
/req/extends/unresolvable Loading a ruleset /conf/extends/resolvable, /conf/extends/unresolvable this draft
/req/extension-properties/ignored Loading a ruleset /conf/extension-properties/ignored this draft
/req/overrides/scope Applying overrides /conf/linter/walkthrough, /conf/overrides/scope-match, /conf/overrides/scope-no-match, /conf/overrides/scope-outside-ruleset-directory, /conf/overrides/scope-ruleset-directory this draft
/req/overrides/order Applying overrides /conf/overrides/order this draft
/req/overrides/extends Applying overrides /conf/overrides/extends, /conf/overrides/extends-with-rules, /conf/overrides/extends-without-rules this draft
/req/overrides/aliases Applying overrides /conf/overrides/aliases-existing-rule, /conf/overrides/aliases-new-rule, /conf/overrides/aliases-outside-globs this draft
/req/overrides/reject-foreign-property Applying overrides /conf/overrides/unknown-property-not-applied, /conf/overrides/unknown-property-applied this draft
/req/overrides/reject-unknown-shorthand Applying overrides /conf/shorthands/override-own-rule, /conf/shorthands/override-unknown-not-applied, /conf/shorthands/override-unknown this draft
/req/overrides/reject-undeclared-alias Applying overrides /conf/overrides/redefined-rule-alias-declared, /conf/overrides/redefined-rule-alias-applied this draft
/req/overrides/checked-when-applied Applying overrides /conf/overrides/unknown-property-not-applied, /conf/shorthands/override-unknown-not-applied this draft
/req/overrides/pointer-scope Applying overrides /conf/overrides/pointer-scope, /conf/overrides/pointer-scope-at this draft
/req/severity/numeric-equivalence Rule state: enabled and severity /conf/severity/numeric-equivalence this draft
/req/severity/default Rule state: enabled and severity /conf/extends/redefine-keeps-properties, /conf/severity/default this draft
/req/rules/recommended Rule state: enabled and severity /conf/rules/recommended-default, /conf/rules/recommended-false this draft
/req/extends/mode-recommended Rule state: enabled and severity /conf/extends/mode-default, /conf/extends/mode-recommended this draft
/req/extends/mode-all Rule state: enabled and severity /conf/extends/mode-all, /conf/extends/mode-all-extra-element this draft
/req/extends/mode-off Rule state: enabled and severity /conf/extends/mode-off this draft
/req/extends/order Rule state: enabled and severity /conf/extends/precedence, /conf/extends/precedence-order this draft
/req/rules/redefinition-merge Rule state: enabled and severity /conf/extends/redefine-keeps-properties this draft
/req/shorthands/severity Rule state: enabled and severity /conf/shorthands/severity this draft
/req/shorthands/false Rule state: enabled and severity /conf/shorthands/false-control, /conf/shorthands/false this draft
/req/shorthands/true Rule state: enabled and severity /conf/linter/walkthrough, /conf/shorthands/true-enables, /conf/shorthands/true-keeps-severity, /conf/shorthands/true-off this draft
/req/severity/off Rule state: enabled and severity /conf/rules/recommended-false, /conf/severity/off-control, /conf/shorthands/false-control, /conf/severity/off, /conf/shorthands/false this draft
/req/formats/identifiers Selecting documents /conf/formats/detect-aas2-6, /conf/formats/detect-aas2-needs-patch, /conf/formats/detect-arazzo, /conf/formats/detect-json-schema-2020-12, /conf/formats/detect-json-schema-loose, /conf/formats/detect-number-version, /conf/formats/detect-oas2, /conf/formats/detect-oas3-1 this draft
/req/formats/detection-scope Selecting documents /conf/formats/detection-declared-only this draft
/req/formats/rule-scope Selecting documents /conf/formats/rule-scope this draft
/req/formats/ruleset-default Selecting documents /conf/formats/ruleset-default, /conf/formats/unrecognized-unscoped, /conf/linter/walkthrough this draft
/req/formats/rule-overrides-ruleset Selecting documents /conf/formats/rule-overrides-ruleset this draft
/req/formats/unscoped-runs-everywhere Selecting documents /conf/formats/unscoped this draft
/req/formats/unrecognized-document Selecting documents /conf/formats/unrecognized-document, /conf/formats/unrecognized-unscoped this draft
/req/given/evaluate Selecting nodes /conf/given/evaluate-all-matches, /conf/given/evaluate-multiple-expressions this draft
/req/given/no-match Selecting nodes /conf/given/match, /conf/given/no-match this draft
/req/given/resolved Selecting nodes /conf/given/resolved-false, /conf/given/resolved-default this draft
/req/given/jsonpath Selecting nodes /conf/given/jsonpath-filter this draft
/req/given/once-per-node Selecting nodes /conf/given/once-per-node this draft
/req/given/alias-expansion Selecting nodes /conf/given/alias-expansion, /conf/linter/walkthrough this draft
/req/aliases/compose Selecting nodes /conf/aliases/compose this draft
/req/aliases/scoped Selecting nodes /conf/aliases/scoped-last-target, /conf/aliases/scoped-no-target, /conf/aliases/scoped-oas2, /conf/aliases/scoped-oas3 this draft
/req/then/apply-each Applying functions /conf/then/apply-each, /conf/then/apply-each-problem this draft
/req/then/field Applying functions /conf/then/field, /conf/then/field-jsonpath, /conf/then/field-missing, /conf/then/field-path this draft
/req/then/field-key Applying functions /conf/then/field-key this draft
/req/then/function-options Applying functions /conf/then/function-options this draft
/req/result/fields Results /conf/result/fields this draft
/req/result/code Results /conf/result/code this draft
/req/result/severity Results /conf/linter/walkthrough, /conf/result/severity, /conf/severity/result this draft
/req/result/range Results /conf/result/range, /conf/result/range-utf16 this draft
/req/result/source Results /conf/result/source this draft
/req/result/order Results /conf/result/order this draft
/req/result/reference-target Results /conf/resolution/shared-reference this draft
/rec/result/documentation-url Results Not tested: a recommendation. this draft
/req/message/override The message /conf/message/override this draft
/req/message/placeholders The message /conf/message/placeholder-description, /conf/message/placeholder-path, /conf/message/placeholder-property this draft
/req/message/default The message /conf/message/default-description this draft
/req/functions/input-types Core functions /conf/functions/input-types this draft
/req/functions/truthy truthy /conf/functions/truthy this draft
/req/functions/falsy falsy /conf/functions/falsy this draft
/req/functions/defined defined /conf/functions/defined this draft
/req/functions/undefined undefined /conf/functions/undefined this draft
/req/functions/pattern pattern /conf/functions/pattern this draft
/req/functions/casing casing /conf/functions/casing, /conf/functions/casing-non-ascii, /conf/functions/casing-types this draft
/req/functions/length length /conf/functions/length, /conf/functions/length-code-units, /conf/functions/length-min this draft
/req/functions/alphabetical alphabetical /conf/functions/alphabetical, /conf/functions/alphabetical-case, /conf/functions/alphabetical-keyed-by, /conf/functions/alphabetical-numbers this draft
/req/functions/enumeration enumeration /conf/functions/enumeration, /conf/functions/enumeration-types this draft
/req/functions/schema schema /conf/functions/schema, /conf/functions/schema-absent, /conf/functions/schema-all-errors, /conf/functions/schema-default-draft, /conf/functions/schema-dialect, /conf/functions/schema-first-error, /conf/functions/schema-format, /conf/functions/schema-ref this draft
/req/functions/xor xor /conf/functions/xor, /conf/functions/xor-null this draft
/req/functions/or or /conf/functions/or, /conf/functions/or-null this draft
/req/functions/unreferenced-reusable-object unreferencedReusableObject /conf/functions/unreferenced-reusable-object this draft

/conf/linter-custom-functions

The contract of custom functions, in any language: how a linter resolves a function a ruleset names, calls it, and reports what it returns. A linter claims it together with a binding. Target: linter Depends on: linter-core.

Identifier Section Tests Since
/req/custom-functions/missing The contract /conf/custom-functions/present, /conf/custom-functions/missing this draft
/req/custom-functions/invoke The contract /conf/custom-functions/invoke this draft
/req/custom-functions/problem-path The contract /conf/custom-functions/problem-path this draft
/req/custom-functions/no-problems The contract /conf/custom-functions/returns-nothing this draft

/conf/linter-custom-functions-javascript

The JavaScript binding of custom functions: how a custom function is written as a JavaScript module, and where a linter finds it. Target: linter Depends on: linter-custom-functions.

Identifier Section Tests Since
/req/custom-functions/javascript-load JavaScript binding /conf/custom-functions/load-custom-dir, /conf/custom-functions/load-default-dir this draft
/req/custom-functions/javascript-promise JavaScript binding /conf/custom-functions/javascript-promise this draft

Appendix B: Conformance tests

Tests are grouped under the requirement they cover. Open a test to see its fixtures and expected outcome.

Conformance class /conf/ruleset-core

/req/ruleset/documentrulesetin section The ruleset document

A ruleset MUST be a JSON RFC8259 or YAML YAML-1.2.2 document.

/conf/ruleset/document-jsonA JSON text whose root is an object is valid.valid

Covers /req/ruleset/document. Fixtures in conformance/ruleset/document-json/.

ruleset.json

{
  "rules": {
    "acme-operation-summary": {
      "given": "$.paths[*][*]",
      "then": {
        "field": "summary",
        "function": "truthy"
      }
    }
  }
}

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/ruleset/document-objectA YAML document whose root is an object is valid.valid

Covers /req/ruleset/document. Fixtures in conformance/ruleset/document-object/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/ruleset/document-syntax-errorA ruleset that is neither JSON nor YAML, here an unclosed flow sequence, is invalid.invalid

Covers /req/ruleset/document. Fixtures in conformance/ruleset/document-syntax-error/.

ruleset.yaml

rules:
  r: [

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/req/ruleset/single-documentrulesetproposedin section The ruleset document

A ruleset written in YAML MUST contain exactly one YAML document.

/conf/ruleset/document-single-yaml-documentA YAML stream with one document, with an explicit document marker, is valid.valid

Covers /req/ruleset/single-document. Fixtures in conformance/ruleset/document-single-yaml-document/.

ruleset.yaml

---
rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/ruleset/document-two-yaml-documentsA YAML stream with two documents is invalid.invalid

Covers /req/ruleset/single-document. Fixtures in conformance/ruleset/document-two-yaml-documents/.

ruleset.yaml

rules: {}
---
rules: {}

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/req/ruleset/schemarulesetin section The ruleset document

The root value of a ruleset MUST be valid against the ruleset schema of this version of this specification, as JSON Schema 2020-12 JSON-SCHEMA-CORE-2020-12 JSON-SCHEMA-VALIDATION-2020-12 defines validity.

/conf/aliases/

/conf/aliases/name-validAn alias name of letters, digits, underscores, and hyphens starting with a letter is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/aliases/name-valid/.

ruleset.yaml

aliases:
  Info_v2-x:
    - $.info
rules:
  r:
    given: "#Info_v2-x"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/aliases/scoped-form-validA scoped alias whose every target has formats and given is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/aliases/scoped-form-valid/.

ruleset.yaml

aliases:
  ParameterObject:
    targets:
      - formats: [oas2]
        given:
          - $.parameters[*]
      - formats: [oas3]
        given:
          - $.components.parameters[*]
rules:
  parameter-name:
    formats: [oas2, oas3]
    given: "#ParameterObject"
    then:
      field: name
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/aliases/simple-form-validA simple alias that is an array of path expressions is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/aliases/simple-form-valid/.

ruleset.yaml

aliases:
  Info:
    - $.info
    - $.externalDocs
rules:
  r:
    given: "#Info"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/aliases/nameAn alias name starting with a digit is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/aliases/name/.

ruleset.yaml

aliases:
  1bad:
    - $.info
rules:
  r:
    given: "#1bad"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/aliases/scoped-form-missing-formatsA scoped alias target without formats is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/aliases/scoped-form-missing-formats/.

ruleset.yaml

aliases:
  Info:
    targets:
      - given:
          - $.info
rules:
  r:
    given: "#Info"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/aliases/simple-form-stringA simple alias given as a bare string is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/aliases/simple-form-string/.

ruleset.yaml

aliases:
  Info: $.info
rules:
  r:
    given: "#Info"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/custom-functions/

/conf/custom-functions/declaration-validfunctions as an array of strings with a functionsDir string is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/custom-functions/declaration-valid/.

ruleset.yaml

functions:
  - isEmptyTitle
functionsDir: ./checks
rules:
  r:
    given: $.info.title
    then:
      function: isEmptyTitle

checks/isEmptyTitle.js

module.exports = (v) => (typeof v === 'string' && v.trim() !== '' ? [] : [{ message: 'Title is empty.' }]);

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/custom-functions/declaration-not-arrayA functions value that is a string is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/custom-functions/declaration-not-array/.

ruleset.yaml

functions: isEmptyTitle
rules:
  r:
    given: $.info.title
    then:
      function: isEmptyTitle

functions/isEmptyTitle.js

module.exports = (targetVal) => {
  if (typeof targetVal !== 'string' || targetVal.trim() === '') {
    return [{ message: 'Title is empty.' }];
  }
  return [];
};

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/extends/

/conf/extends/entry-forms-validA string entry and tuples with each of the three modes are valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/extends/entry-forms-valid/.

ruleset.yaml

extends:
  - ./base.yaml
  - [./a.yaml, recommended]
  - [./b.yaml, all]
  - [./c.yaml, "off"]

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: falsy

a.yaml

rules:
  a-rule:
    given: $
    then:
      function: falsy

b.yaml

rules:
  b-rule:
    given: $
    then:
      function: falsy

c.yaml

rules:
  c-rule:
    given: $
    then:
      function: falsy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/extends/entry-tuple-lengthAn extends tuple with a third element is valid; elements after the mode are ignored.valid

Covers /req/ruleset/schema. Fixtures in conformance/extends/entry-tuple-length/.

ruleset.yaml

extends:
  - [./base.yaml, all, extra]

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/extends/shape-arrayA ruleset whose extends is an array mixing a string and a tuple is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/extends/shape-array/.

ruleset.yaml

extends:
  - ./base.yaml
  - [./other.yaml, all]

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: truthy

other.yaml

rules:
  other-rule:
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/extends/shape-stringA ruleset whose extends is a single string is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/extends/shape-string/.

ruleset.yaml

extends: ./base.yaml

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/extends/entry-invalid-modeAn extends tuple with the mode strict is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/extends/entry-invalid-mode/.

ruleset.yaml

extends:
  - [./base.yaml, strict]

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/extends/shape-objectA ruleset whose extends is an object is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/extends/shape-object/.

ruleset.yaml

extends:
  ruleset: ./base.yaml

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/formats/

/conf/formats/shape-validformats as an array of strings on the ruleset, a rule, an override, and an alias target is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/formats/shape-valid/.

ruleset.yaml

formats: [oas3]
aliases:
  Params:
    targets:
      - formats: [oas3]
        given:
          - $.components.parameters[*]
rules:
  r:
    formats: [oas3_1]
    given: "#Params"
    then:
      function: truthy
overrides:
  - files: ["legacy/**"]
    formats: [oas3_0]
    rules:
      r: warn

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/formats/shape-stringA ruleset whose formats is a string is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/formats/shape-string/.

ruleset.yaml

formats: oas3
rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/formats/unknown-identifierA rule whose formats names an identifier outside the table, oas31, is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/formats/unknown-identifier/.

ruleset.yaml

rules:
  r:
    formats: [oas31]
    given: $.info
    then:
      field: title
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/given/

/conf/given/path-expression-formsA JSONPath expression starting with $ and an alias reference starting with # are both valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/given/path-expression-forms/.

ruleset.yaml

aliases:
  Info:
    - $.info
rules:
  r:
    given:
      - $.paths
      - "#Info"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/given/presentA rule with given and then is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/given/present/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/given/shape-validgiven as a single path expression and as an array of them are both valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/given/shape-valid/.

ruleset.yaml

rules:
  one:
    given: $.info
    then:
      function: truthy
  many:
    given:
      - $.info.title
      - $.info.description
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/given/bare-property-nameA rule whose given is a bare property name is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/given/bare-property-name/.

ruleset.yaml

rules:
  r:
    given: paths
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/given/requiredA rule with then but no given is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/given/required/.

ruleset.yaml

rules:
  r:
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/given/shape-empty-arrayA rule with given: [] is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/given/shape-empty-array/.

ruleset.yaml

rules:
  r:
    given: []
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/given/shape-objectA rule whose given is an object is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/given/shape-object/.

ruleset.yaml

rules:
  r:
    given:
      path: $.info
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/overrides/

/conf/overrides/file-scoped-validA file-scoped override with rules, extends, formats, parserOptions, and aliases is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/file-scoped-valid/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["legacy/**"]
    extends: ./base.yaml
    formats: [oas3]
    parserOptions:
      duplicateKeys: warn
    aliases:
      Info:
        - $.info
    rules:
      acme-operation-summary: warn

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: falsy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/overrides/mixed-globsAn override whose files mix a pointer glob and a plain glob is file-scoped, so it can define rules with shorthands and is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/mixed-globs/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
overrides:
  - files: ["api.yaml#/paths", "**/*.yaml"]
    rules:
      r: hint

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/overrides/pointer-scoped-validA pointer-scoped override whose rules are severities only is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/pointer-scoped-valid/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["document.yaml#/paths/~1legacy"]
    rules:
      acme-operation-summary: "off"

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/overrides/shape-validAn override with a non-empty files array is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/shape-valid/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["legacy/**"]
    rules:
      acme-operation-summary: warn

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/overrides/file-scoped-emptyA file-scoped override with neither extends nor rules is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/file-scoped-empty/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["**/*.yaml"]

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/overrides/pointer-scoped-extendsA pointer-scoped override with extends is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/pointer-scoped-extends/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["document.yaml#/paths"]
    extends: ./base.yaml
    rules:
      acme-operation-summary: error

base.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/overrides/pointer-scoped-rule-objectA pointer-scoped override that defines a rule object is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/pointer-scoped-rule-object/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["document.yaml#/paths"]
    rules:
      acme-operation-summary:
        given: $
        then:
          function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/overrides/shape-emptyAn empty overrides array is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/shape-empty/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides: []

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/overrides/shape-no-filesAn override without files is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/overrides/shape-no-files/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - rules:
      acme-operation-summary: error

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/parser-options/

/conf/parser-options/shape-validBoth parser options with severity values are valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/parser-options/shape-valid/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
parserOptions:
  duplicateKeys: warn
  incompatibleValues: hint

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/parser-options/shape-extension-propertyAn extension property inside parserOptions is invalid: the permission covers the root and rules only, and parserOptions admits no other properties.invalid

Covers /req/ruleset/schema, /per/extension-properties/anywhere. Fixtures in conformance/parser-options/shape-extension-property/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: truthy
parserOptions:
  x-note: not permitted here

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/parser-options/shape-unknown-optionAn unknown parser option is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/parser-options/shape-unknown-option/.

ruleset.yaml

rules:
  quiet:
    given: $.nothing
    then:
      function: truthy
parserOptions:
  trailingCommas: error

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/parser-options/shape-unknown-severityduplicateKeys: critical is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/parser-options/shape-unknown-severity/.

ruleset.yaml

rules:
  quiet:
    given: $.nothing
    then:
      function: truthy
parserOptions:
  duplicateKeys: critical

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/rule/

/conf/rule/all-propertiesA rule using every property with its documented type is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/rule/all-properties/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    message: "{{description}}"
    severity: error
    formats: [oas3]
    documentationUrl: https://engineering.acme.com/api/governance#summary
    recommended: true
    resolved: true
    tags: [style, docs]
    type: style
    extensions:
      owner: platform-team
    x-jira: GOV-142
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/rule/then-presentA rule with then and given is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/rule/then-present/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/rule/recommended-not-booleanA rule whose recommended is a string is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/rule/recommended-not-boolean/.

ruleset.yaml

rules:
  r:
    recommended: "yes"
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/rule/then-requiredA rule with given but no then is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/rule/then-required/.

ruleset.yaml

rules:
  r:
    given: $

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/rule/type-unknownA rule whose type is neither style nor validation is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/rule/type-unknown/.

ruleset.yaml

rules:
  r:
    type: lint
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/rule/unknown-propertyA rule with the unknown property severty is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/rule/unknown-property/.

ruleset.yaml

rules:
  r:
    severty: error
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/

/conf/ruleset/all-propertiesA ruleset using every root property with its documented type is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/all-properties/.

ruleset.yaml

description: Acme API style guide
documentationUrl: https://engineering.acme.com/api/governance
formats: [oas3]
extends: ./base.yaml
aliases:
  Info:
    - $.info
overrides:
  - files: ["legacy/**"]
    rules:
      base-rule: warn
parserOptions:
  duplicateKeys: warn
functions:
  - isEmptyTitle
functionsDir: ./checks
rules:
  title-filled:
    given: $.info.title
    then:
      function: isEmptyTitle

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: falsy

checks/isEmptyTitle.js

module.exports = (v) => (typeof v === 'string' && v.trim() !== '' ? [] : [{ message: 'Title is empty.' }]);

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/ruleset/contributes-extends-onlyA ruleset with only extends is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/contributes-extends-only/.

ruleset.yaml

extends: ./base.yaml

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: falsy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/ruleset/extension-root-propertyA ruleset with the extension property x-owner at the root is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/extension-root-property/.

ruleset.yaml

x-owner: platform-team
rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/ruleset/contributes-emptyA ruleset with only description is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/contributes-empty/.

ruleset.yaml

description: Says nothing.

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/description-not-stringA ruleset whose description is a number is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/description-not-string/.

ruleset.yaml

description: 1
rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/document-root-arrayA ruleset whose root value is an array is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/document-root-array/.

ruleset.yaml

- rules: {}

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/documentation-url-intranetA ruleset whose documentationUrl names a single-label intranet host, http://wiki/docs, is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/documentation-url-intranet/.

ruleset.yaml

documentationUrl: http://wiki/docs
rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/documentation-url-invalidA ruleset whose documentationUrl is not a URI is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/documentation-url-invalid/.

ruleset.yaml

documentationUrl: "governance docs"
rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/documentation-url-mailtoA ruleset whose documentationUrl is a mailto: URI is invalid: only http, https, and ftp URLs are accepted.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/documentation-url-mailto/.

ruleset.yaml

documentationUrl: "mailto:api-governance@acme.example"
rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/rules-not-objectA ruleset whose rules is an array is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/rules-not-object/.

ruleset.yaml

rules:
  - r

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/ruleset/unknown-root-propertyA ruleset with rules and the unknown root property rule is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/ruleset/unknown-root-property/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: truthy
rule:
  s:
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/severity/

/conf/severity/values-nameA rule with severity: hint is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/severity/values-name/.

ruleset.yaml

rules:
  r:
    severity: hint
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/severity/values-numberA rule with severity: 3 is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/severity/values-number/.

ruleset.yaml

rules:
  r:
    severity: 3
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/severity/values-invalidA rule with severity: critical is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/severity/values-invalid/.

ruleset.yaml

rules:
  r:
    severity: critical
    given: $
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/shorthands/

/conf/shorthands/values-validEvery severity name and both booleans are valid in the shorthand position.valid

Covers /req/ruleset/schema. Fixtures in conformance/shorthands/values-valid/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: hint
  s: false

a.yaml

rules:
  r:
    given: $
    then:
      function: falsy
  s:
    given: $
    then:
      function: falsy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/shorthands/numericA numeric severity in the shorthand position is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/shorthands/numeric/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: 1

a.yaml

rules:
  r:
    recommended: false
    severity: warn
    given: $
    then:
      function: falsy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/shorthands/unknown-nameAn unknown name in the shorthand position is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/shorthands/unknown-name/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: critical

a.yaml

rules:
  r:
    recommended: false
    severity: warn
    given: $
    then:
      function: falsy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/then/

/conf/then/invocation-form-validAn invocation with function, field, and functionOptions of the right types is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/then/invocation-form-valid/.

ruleset.yaml

rules:
  r:
    given: $.info
    then:
      field: title
      function: length
      functionOptions:
        max: 120

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/then/shape-empty-arrayA rule with then: [] is valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/then/shape-empty-array/.

ruleset.yaml

rules:
  r:
    given: $
    then: []

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/then/shape-validthen as a single invocation and as an array of them are both valid.valid

Covers /req/ruleset/schema. Fixtures in conformance/then/shape-valid/.

ruleset.yaml

rules:
  one:
    given: $.info
    then:
      function: truthy
  many:
    given: $.info
    then:
      - function: truthy
      - field: title
        function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/then/function-missingAn invocation without function is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/then/function-missing/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      field: title

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/then/options-not-objectAn invocation whose functionOptions is a string is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/then/options-not-object/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: pattern
      functionOptions: "^a"

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/then/shape-stringA rule whose then is a string is invalid.invalid

Covers /req/ruleset/schema. Fixtures in conformance/then/shape-string/.

ruleset.yaml

rules:
  r:
    given: $
    then: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/req/shorthands/inherited-onlyrulesetin section Shorthands

A shorthand in the root rules MUST name a rule the ruleset inherits through extends.

/conf/shorthands/inheritedA shorthand for a rule inherited through extends is valid.valid

Covers /req/shorthands/inherited-only. Fixtures in conformance/shorthands/inherited/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: error

a.yaml

rules:
  r:
    given: $
    then:
      function: falsy
  s:
    given: $
    then:
      function: falsy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/shorthands/not-inheritedA shorthand for a rule the ruleset does not inherit is invalid.invalid

Covers /req/shorthands/inherited-only. Fixtures in conformance/shorthands/not-inherited/.

ruleset.yaml

rules:
  r: error

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/req/aliases/no-cyclerulesetin section aliases

An alias MUST NOT refer to itself, directly or through other aliases.

/conf/aliases/chainAn alias defined through another alias, without a cycle, is valid.valid

Covers /req/aliases/no-cycle. Fixtures in conformance/aliases/chain/.

ruleset.yaml

aliases:
  Info:
    - $.info
  InfoTitle:
    - "#Info.title"
rules:
  r:
    given: "#InfoTitle"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/aliases/cycleTwo aliases that refer to each other are invalid.invalid

Covers /req/aliases/no-cycle. Fixtures in conformance/aliases/cycle/.

ruleset.yaml

aliases:
  A:
    - "#B"
  B:
    - "#A"
rules:
  r:
    given: "#A"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/req/given/alias-definedrulesetin section given

An alias reference in a rule MUST name an alias declared under aliases in the ruleset whose rules contain the rule or, for a rule in a file-scoped override, in that override or that ruleset. When the rule redefines a rule inherited through extends, the ruleset that defines the inherited rule MUST declare that alias too.

/conf/given/alias-declaredA rule referencing an alias the ruleset declares is valid.valid

Covers /req/given/alias-defined. Fixtures in conformance/given/alias-declared/.

ruleset.yaml

aliases:
  Info:
    - $.info
rules:
  r:
    given: "#Info.title"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/given/alias-redefined-bothA rule that redefines an inherited rule with an alias both rulesets declare is valid.valid

Covers /req/given/alias-defined. Fixtures in conformance/given/alias-redefined-both/.

ruleset.yaml

extends: ./base.yaml
aliases:
  X:
    - $.info
rules:
  base-rule:
    given: "#X"
    then:
      field: title
      function: truthy

base.yaml

aliases:
  X:
    - $.info
rules:
  base-rule:
    given: $.info
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/given/alias-redefined-ownA rule that redefines an inherited rule with an alias only the extending ruleset declares is invalid.invalid

Covers /req/given/alias-defined. Fixtures in conformance/given/alias-redefined-own/.

ruleset.yaml

extends: ./base.yaml
aliases:
  Y:
    - $.info
rules:
  base-rule:
    given: "#Y"
    then:
      field: title
      function: truthy

base.yaml

rules:
  base-rule:
    given: $.info
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/given/alias-undefinedA rule referencing an alias the ruleset does not declare is invalid.invalid

Covers /req/given/alias-defined. Fixtures in conformance/given/alias-undefined/.

ruleset.yaml

rules:
  r:
    given: "#Missing"
    then:
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/req/then/function-knownrulesetin section then

function MUST name a #core-functions|core function or a custom function declared in functions.

/conf/then/function-coreA rule naming a core function is valid.valid

Covers /req/then/function-known. Fixtures in conformance/then/function-core/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/then/function-unknownA rule naming a function that is neither core nor declared is invalid.invalid

Covers /req/then/function-known. Fixtures in conformance/then/function-unknown/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: nonexistent

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/req/then/function-options-validrulesetin section then

When function names a core function, functionOptions MUST satisfy that function’s options in #core-functions: only the options its table lists, with the types listed, every required option present, and no functionOptions at all for a function that takes no options.

/conf/then/options-validCore functions with options that match their tables are valid.valid

Covers /req/then/function-options-valid. Fixtures in conformance/then/options-valid/.

ruleset.yaml

rules:
  a:
    given: $.name
    then:
      function: casing
      functionOptions:
        type: camel
        disallowDigits: true
  b:
    given: $.tags
    then:
      function: alphabetical
  c:
    given: $.name
    then:
      function: length
      functionOptions:
        min: 1

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/then/options-on-no-option-functiontruthy with options is invalid, because it takes none.invalid

Covers /req/then/function-options-valid. Fixtures in conformance/then/options-on-no-option-function/.

ruleset.yaml

rules:
  r:
    given: $.name
    then:
      function: truthy
      functionOptions:
        strict: true

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/then/options-required-missinglength with empty options is invalid, because it needs min or max.invalid

Covers /req/then/function-options-valid. Fixtures in conformance/then/options-required-missing/.

ruleset.yaml

rules:
  r:
    given: $.name
    then:
      function: length
      functionOptions: {}

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/conf/then/options-unknown-valuecasing with type: lower, which is not a case the function knows, is invalid.invalid

Covers /req/then/function-options-valid. Fixtures in conformance/then/options-unknown-value/.

ruleset.yaml

rules:
  r:
    given: $.name
    then:
      function: casing
      functionOptions:
        type: lower

Procedure

Load the ruleset with a linter.

Expected

The ruleset is invalid: a linter rejects it.

/per/extension-properties/anywhererulesetin section Extension properties

A ruleset MAY have extension properties at its root and on any rule, with any value.

/conf/extension-properties/acceptedA ruleset with extension properties of several types at the root and on a rule is accepted.valid

Covers /per/extension-properties/anywhere. Fixtures in conformance/extension-properties/accepted/.

ruleset.yaml

x-owner: platform-team
x-review:
  last: 2026-10-01
  ticket: GOV-142
rules:
  acme-operation-summary:
    x-jira: GOV-142
    x-rationale: Summaries feed the generated docs.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Load the ruleset with a linter.

Expected

The ruleset is valid: a linter loads it.

/conf/parser-options/shape-extension-propertyAn extension property inside parserOptions is invalid: the permission covers the root and rules only, and parserOptions admits no other properties.invalidalso covers /req/ruleset/schema; shown there

Conformance class /conf/linter-core

/req/document/any-rootlinterin section The document model

A linter MUST lint a document whose root value is not an object, and $ MUST select that root value.

/conf/document/empty-fileAn empty file is the document null, and a rule on $ sees it.1 result

Covers /req/document/any-root. Fixtures in conformance/document/empty-file/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: truthy

document.yaml


     

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": []
  }
]
/conf/document/root-arrayA document whose root is an array is linted, and $ selects the array.1 result

Covers /req/document/any-root. Fixtures in conformance/document/root-array/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy

document.yaml

[1, 2]

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": []
  }
]

/req/document/encodinglinterin section The document model

A linter MUST read a document and a ruleset as UTF-8.

/conf/document/utf8-keysA document is read as UTF-8, so non-ASCII keys arrive intact in path.2 results

Covers /req/document/encoding. Fixtures in conformance/document/utf8-keys/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      field: "é"
      function: truthy
  s:
    given: $
    then:
      field: "😀"
      function: truthy

document.yaml

é: ""
😀: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "\u00e9"
    ]
  },
  {
    "code": "s",
    "path": [
      "\ud83d\ude00"
    ]
  }
]

/req/parsing/syntax-errorlinterin section Parsing

When a document has a syntax error, a linter MUST produce a result with the code parser and the severity error.

/conf/parsing/syntax-errorA YAML document with an unclosed flow sequence is reported as a parser error.1 result

Covers /req/parsing/syntax-error. Fixtures in conformance/parsing/syntax-error/.

ruleset.yaml

rules:
  r:
    given: $.nothing
    then:
      function: truthy

document.yaml

a: [unclosed

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "parser",
    "severity": 0
  }
]

/req/parsing/several-documentslinterin section Parsing

When a YAML document contains several documents, a linter MUST produce a result with the code parser and the severity error, and MUST lint the first document.

/conf/parsing/several-documentsA YAML file with two documents gets a parser error, and the rules run on the first document.2 results

Covers /req/parsing/several-documents. Fixtures in conformance/parsing/several-documents/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy

document.yaml

a: 1
---
b: 2

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "parser",
    "severity": 0
  },
  {
    "code": "r",
    "path": []
  }
]

/req/parser-options/duplicate-keyslinterin section Parsing

When a document has a duplicate key, a linter MUST produce a result with the code parser and the severity of duplicateKeys, error when it is not set, and MUST NOT produce one when it is off.

/conf/parser-options/duplicate-keys-defaultA duplicate key is reported as an error by default.1 result

Covers /req/parser-options/duplicate-keys. Fixtures in conformance/parser-options/duplicate-keys-default/.

ruleset.yaml

rules:
  quiet:
    given: $.nothing
    then:
      function: truthy

document.yaml

info:
  title: One
  title: Two

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "parser",
    "severity": 0
  }
]
/conf/parser-options/duplicate-keys-warnWith duplicateKeys: warn, a duplicate key is reported as a warning.1 result

Covers /req/parser-options/duplicate-keys. Fixtures in conformance/parser-options/duplicate-keys-warn/.

ruleset.yaml

rules:
  quiet:
    given: $.nothing
    then:
      function: truthy
parserOptions:
  duplicateKeys: warn

document.yaml

info:
  title: One
  title: Two

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "parser",
    "severity": 1
  }
]
/conf/parser-options/duplicate-keys-offWith duplicateKeys: off, a duplicate key is not reported.0 results

Covers /req/parser-options/duplicate-keys. Fixtures in conformance/parser-options/duplicate-keys-off/.

ruleset.yaml

rules:
  quiet:
    given: $.nothing
    then:
      function: truthy
parserOptions:
  duplicateKeys: off

document.yaml

info:
  title: One
  title: Two

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces no results.

/req/parser-options/incompatible-valueslinterin section Parsing

When a YAML document has a mapping key that is not a string scalar, such as 1, true, or null, a linter MUST produce a result with the code parser and the severity of incompatibleValues, error when it is not set, and MUST NOT produce one when it is off.

/conf/parser-options/incompatible-valuesA YAML mapping key that is a number, not a string, has no JSON representation and is reported as an error by default.1 result

Covers /req/parser-options/incompatible-values. Fixtures in conformance/parser-options/incompatible-values/.

ruleset.yaml

rules:
  quiet:
    given: $.nothing
    then:
      function: truthy

document.yaml

1: x

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "parser",
    "severity": 0
  }
]

/req/resolution/locallinterin section Reference resolution

A linter MUST replace a reference whose $ref is a fragment with the value the JSON Pointer names in the same document.

/conf/resolution/localA $ref to a pointer in the same document is replaced by the referenced schema, whose type the rule then sees.1 result

Covers /req/resolution/local. Fixtures in conformance/resolution/local/.

ruleset.yaml

rules:
  id-is-string:
    given: $.components.schemas.Pet.properties.id.type
    then:
      function: enumeration
      functionOptions:
        values: [string]

document.yaml

components:
  schemas:
    Id:
      type: integer
    Pet:
      properties:
        id:
          $ref: "#/components/schemas/Id"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "id-is-string"
  }
]

/req/resolution/externallinterin section Reference resolution

A linter MUST replace a reference whose $ref names another document with the value its fragment names in that document, after resolving the $ref against the location of the referencing document.

/conf/resolution/externalA $ref to another file, relative to the document, is replaced by the referenced schema.1 result

Covers /req/resolution/external. Fixtures in conformance/resolution/external/.

ruleset.yaml

rules:
  id-is-string:
    given: $.components.schemas.Pet.properties.id.type
    then:
      function: enumeration
      functionOptions:
        values: [string]

document.yaml

components:
  schemas:
    Pet:
      properties:
        id:
          $ref: "./schemas.yaml#/Id"

schemas.yaml

Id:
  type: integer

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "id-is-string"
  }
]

/req/resolution/cyclelinterin section Reference resolution

A linter MUST resolve a document whose references form a cycle without failing, and MUST evaluate rules against it.

/conf/resolution/cycleA schema that references itself resolves without error, and a rule sees its type.1 result

Covers /req/resolution/cycle. Fixtures in conformance/resolution/cycle/.

ruleset.yaml

rules:
  node-is-string:
    given: $.components.schemas.Node.type
    then:
      function: enumeration
      functionOptions:
        values: [string]

document.yaml

components:
  schemas:
    Node:
      type: object
      properties:
        children:
          type: array
          items:
            $ref: "#/components/schemas/Node"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "node-is-string"
  }
]

/req/resolution/unresolvablelinterin section Reference resolution

When a $ref cannot be resolved, a linter MUST produce a result with the code invalid-ref and the severity error, and MUST leave the reference object in place for the rules.

/conf/resolution/unresolvableA $ref to a file that does not exist is reported as invalid-ref at severity error.1 result

Covers /req/resolution/unresolvable. Fixtures in conformance/resolution/unresolvable/.

ruleset.yaml

rules:
  quiet:
    given: $.nothing
    then:
      function: truthy

document.yaml

components:
  schemas:
    Pet:
      $ref: "./missing.yaml#/Pet"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "invalid-ref",
    "severity": 0
  }
]
/conf/resolution/unresolvable-keptAfter a $ref fails to resolve, a rule still sees the reference object.2 results

Covers /req/resolution/unresolvable. Fixtures in conformance/resolution/unresolvable-kept/.

ruleset.yaml

rules:
  ref-kept:
    given: $.components.schemas.Pet
    then:
      field: $ref
      function: undefined

document.yaml

components:
  schemas:
    Pet:
      $ref: "./missing.yaml#/Pet"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "invalid-ref",
    "severity": 0
  },
  {
    "code": "ref-kept",
    "path": [
      "components",
      "schemas",
      "Pet",
      "$ref"
    ]
  }
]

/req/linter/reject-invalidlinterin section Loading a ruleset

A linter MUST reject a ruleset that violates any requirement whose conformance target is ruleset, or that extends, directly or through another ruleset’s extends, a ruleset that violates one, and MUST NOT produce results from it.

/conf/linter/accept-validA valid ruleset is loaded and linting produces its results.2 results

Covers /req/linter/reject-invalid, /req/linter/accept-valid. Fixtures in conformance/linter/accept-valid/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary"
  },
  {
    "code": "acme-operation-summary"
  }
]
/conf/extends/base-invalidA ruleset that extends a ruleset with an invalid given is rejected.rejected

Covers /req/linter/reject-invalid. Fixtures in conformance/extends/base-invalid/.

ruleset.yaml

extends: ./base.yaml

base.yaml

rules:
  r:
    given: paths
    then:
      function: truthy

Procedure

Lint an empty document with ruleset.yaml and collect the results as JSON.

Expected

The linter rejects the ruleset and produces no results.

/conf/linter/reject-invalidGiven an invalid ruleset, the linter rejects it and produces no results.rejected

Covers /req/linter/reject-invalid. Fixtures in conformance/linter/reject-invalid/.

ruleset.yaml

- rules: {}

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

The linter rejects the ruleset and produces no results.

/req/linter/accept-validlinterin section Loading a ruleset

A linter MUST load a ruleset that satisfies every requirement whose conformance target is ruleset, when every ruleset it extends and every custom function it names can be loaded, unless /req/overrides/checked-when-applied has it rejected for the document being linted.

/conf/linter/accept-validA valid ruleset is loaded and linting produces its results.2 resultsalso covers /req/linter/reject-invalid; shown there

/req/extends/loadlinterin section Loading a ruleset

A linter MUST load every ruleset identified in extends and make each of its rules available to the extending ruleset under its rule name.

/conf/extends/loadA rule defined only in the extended ruleset is available to the extending one by name.2 results

Covers /req/extends/load. Fixtures in conformance/extends/load/.

ruleset.yaml

extends: ./base.yaml
rules:
  acme-operation-summary: error

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

base.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary"
  },
  {
    "code": "acme-operation-summary"
  }
]
/conf/linter/walkthroughThe running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document.3 results

Covers /req/extends/load, /req/overrides/scope, /req/shorthands/true, /req/formats/ruleset-default, /req/given/alias-expansion, /req/result/severity. Fixtures in conformance/linter/walkthrough/.

ruleset.yaml

extends: ./acme-base.yaml
formats: [oas3]
aliases:
  Operation:
    - "$.paths[*]['get','put','post','delete']"
rules:
  operation-id-camel-case: true
  operation-has-tags:
    description: Every operation is tagged.
    severity: hint
    given: "#Operation"
    then:
      field: tags
      function: defined
overrides:
  - files: ["legacy/**"]
    rules:
      operation-summary: error

legacy/petstore.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      operationId: list_pets
      responses:
        "200":
          description: OK
    post:
      summary: Add a pet
      operationId: addPet
      tags: [pets]
      responses:
        "201":
          description: Created

acme-base.yaml

rules:
  operation-summary:
    description: Every operation needs a summary.
    severity: warn
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
  operation-id-camel-case:
    description: operationId is written in camelCase.
    recommended: false
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace'].operationId
    then:
      function: casing
      functionOptions:
        type: camel

Procedure

Lint legacy/petstore.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 3 results, matching on the fields given, in any order:

[
  {
    "code": "operation-summary",
    "path": [
      "paths",
      "/pets",
      "get"
    ],
    "severity": 0
  },
  {
    "code": "operation-has-tags",
    "path": [
      "paths",
      "/pets",
      "get"
    ],
    "severity": 3
  },
  {
    "code": "operation-id-camel-case",
    "path": [
      "paths",
      "/pets",
      "get",
      "operationId"
    ],
    "severity": 1
  }
]

/req/extends/unresolvablelinterin section Loading a ruleset

A linter MUST reject a ruleset whose extends identifies a ruleset it cannot load.

/conf/extends/resolvableA ruleset extending a file that exists is loaded and the inherited rule runs.1 result

Covers /req/extends/unresolvable. Fixtures in conformance/extends/resolvable/.

ruleset.yaml

extends: ./base.yaml

document.yaml

{}

base.yaml

rules:
  base-rule:
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "base-rule"
  }
]
/conf/extends/unresolvableA ruleset extending a file that does not exist is rejected.rejected

Covers /req/extends/unresolvable. Fixtures in conformance/extends/unresolvable/.

ruleset.yaml

extends: ./does-not-exist.yaml

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

The linter rejects the ruleset and produces no results.

/req/extension-properties/ignoredlinterin section Loading a ruleset

A linter MUST NOT let an extension property change which results it produces.

/conf/extension-properties/ignoredExtension properties at the root and on a rule change nothing about the results.2 results

Covers /req/extension-properties/ignored. Fixtures in conformance/extension-properties/ignored/.

ruleset.yaml

x-owner: platform-team
rules:
  acme-operation-summary:
    x-jira: GOV-142
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary"
  },
  {
    "code": "acme-operation-summary"
  }
]

/req/overrides/scopelinterin section Applying overrides

A linter MUST apply an override to a document only when the document’s path, relative to the directory of the ruleset file and with / as the separator, matches one of the override’s files globs.

/conf/linter/walkthroughThe running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document.3 resultsalso covers /req/extends/load, /req/shorthands/true, /req/formats/ruleset-default, /req/given/alias-expansion, /req/result/severity; shown there

/conf/overrides/scope-matchA document inside the override’s globs gets the override’s severity.2 results

Covers /req/overrides/scope. Fixtures in conformance/overrides/scope-match/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: warn
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["legacy/**"]
    rules:
      acme-operation-summary: error

legacy/document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint legacy/document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "severity": 0
  },
  {
    "severity": 0
  }
]
/conf/overrides/scope-no-matchA document outside the override’s globs keeps the base severity.2 results

Covers /req/overrides/scope. Fixtures in conformance/overrides/scope-no-match/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: warn
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["legacy/**"]
    rules:
      acme-operation-summary: error

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "severity": 1
  },
  {
    "severity": 1
  }
]
/conf/overrides/scope-outside-ruleset-directoryThe same glob does not match a document in a legacy/ directory that is not next to the ruleset, whatever the working directory.1 result

Covers /req/overrides/scope. Fixtures in conformance/overrides/scope-outside-ruleset-directory/.

rules/ruleset.yaml

rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy
overrides:
  - files: ['legacy/**']
    rules:
      r: error

legacy/a.yaml

info: {}

Procedure

Lint legacy/a.yaml with rules/ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 1
  }
]
/conf/overrides/scope-ruleset-directoryA glob is matched against the document’s path relative to the ruleset file’s directory, so legacy/** matches a document in legacy/ next to the ruleset.1 result

Covers /req/overrides/scope. Fixtures in conformance/overrides/scope-ruleset-directory/.

rules/ruleset.yaml

rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy
overrides:
  - files: ['legacy/**']
    rules:
      r: error

rules/legacy/a.yaml

info: {}

Procedure

Lint rules/legacy/a.yaml with rules/ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 0
  }
]

/req/overrides/orderlinterin section Applying overrides

When several overrides match a document, a linter MUST apply them in array order, each on top of the previous, and all of them on top of the ruleset itself.

/conf/overrides/orderOf two overrides that both match, the later one wins.2 results

Covers /req/overrides/order. Fixtures in conformance/overrides/order/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: warn
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    rules:
      acme-operation-summary: error
  - files: ["**/*.yaml"]
    rules:
      acme-operation-summary: hint

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "severity": 3
  },
  {
    "severity": 3
  }
]

/req/overrides/extendslinterin section Applying overrides

For a document a file-scoped override applies to, a linter MUST load the rulesets the override’s extends identifies and make their rules available in addition to those of the rulesets the ruleset extends, or, when the override has no rules, in place of them.

/conf/overrides/extendsAn override’s extends adds rules for the documents it matches.1 result

Covers /req/overrides/extends. Fixtures in conformance/overrides/extends/.

ruleset.yaml

rules:
  base-rule:
    given: $.nothing
    then:
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    extends: ./extra.yaml

document.yaml

{}

extra.yaml

rules:
  extra-rule:
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "extra-rule"
  }
]
/conf/overrides/extends-with-rulesAn applying override with extends and rules adds to the rulesets the ruleset extends: a, b, and r all run.3 results

Covers /req/overrides/extends. Fixtures in conformance/overrides/extends-with-rules/.

ruleset.yaml

extends: ./a.yaml
rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    extends: ./b.yaml
    rules:
      r: error

document.yaml

openapi: 3.1.0
info:
  version: "1"

a.yaml

rules:
  a:
    given: $.info
    then:
      field: title
      function: truthy

b.yaml

rules:
  b:
    given: $.info
    then:
      field: title
      function: truthy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 3 results, matching on the fields given, in any order:

[
  {
    "code": "a"
  },
  {
    "code": "b"
  },
  {
    "code": "r",
    "severity": 0
  }
]
/conf/overrides/extends-without-rulesAn applying override with extends and no rules replaces the rulesets the ruleset extends: b and the ruleset’s own r run, a does not.2 results

Covers /req/overrides/extends. Fixtures in conformance/overrides/extends-without-rules/.

ruleset.yaml

extends: ./a.yaml
rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    extends: ./b.yaml

document.yaml

openapi: 3.1.0
info:
  version: "1"

a.yaml

rules:
  a:
    given: $.info
    then:
      field: title
      function: truthy

b.yaml

rules:
  b:
    given: $.info
    then:
      field: title
      function: truthy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "b"
  },
  {
    "code": "r"
  }
]

/req/overrides/aliaseslinterin section Applying overrides

For a document a file-scoped override applies to, a linter MUST resolve the alias references of each rule the override defines and the ruleset does not against the override’s aliases and the ruleset’s, the override’s alias taking precedence for a name both declare, and MUST resolve the alias references of every other rule as if the override declared no aliases.

/conf/overrides/aliases-existing-ruleAn alias an override declares does not change a rule the ruleset defines: the rule keeps the ruleset’s alias and only its severity changes.1 result

Covers /req/overrides/aliases. Fixtures in conformance/overrides/aliases-existing-rule/.

ruleset.yaml

aliases:
  Info:
    - $..info
rules:
  info-description:
    given: "#Info.description"
    then:
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    aliases:
      Info:
        - $.nothing
    rules:
      info-description: error

document.yaml

info:
  title: Petstore
  description: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "info-description",
    "severity": 0
  }
]
/conf/overrides/aliases-new-ruleA rule an override defines resolves aliases against the override’s, which take precedence over the ruleset’s alias of the same name.1 result

Covers /req/overrides/aliases. Fixtures in conformance/overrides/aliases-new-rule/.

ruleset.yaml

aliases:
  Info:
    - $.nothing
rules:
  base:
    given: $.info
    then:
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    aliases:
      Info:
        - $..info
    rules:
      override-rule:
        given: "#Info.description"
        then:
          function: truthy

document.yaml

info:
  title: Petstore
  description: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "override-rule",
    "path": [
      "info",
      "description"
    ]
  }
]
/conf/overrides/aliases-outside-globsOutside the override’s globs its aliases have no effect, and the rule keeps its default severity.1 result

Covers /req/overrides/aliases. Fixtures in conformance/overrides/aliases-outside-globs/.

ruleset.yaml

aliases:
  Info:
    - $..info
rules:
  info-description:
    given: "#Info.description"
    then:
      function: truthy
overrides:
  - files: ["legacy/**"]
    aliases:
      Info:
        - $.nothing
    rules:
      info-description: error

document.yaml

info:
  title: Petstore
  description: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "info-description",
    "severity": 1
  }
]

/req/overrides/reject-foreign-propertylinterin section Applying overrides

For a document a file-scoped override applies to, a linter MUST reject the ruleset when the override has a property the Ruleset Object does not have.

/conf/overrides/unknown-property-not-appliedThe same override, applying to no document being linted, does not stop the ruleset from loading.1 result

Covers /req/overrides/reject-foreign-property, /req/overrides/checked-when-applied. Fixtures in conformance/overrides/unknown-property-not-applied/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
overrides:
  - files: ["legacy/**"]
    foo: bar
    rules:
      r: error

document.yaml

{}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 1
  }
]
/conf/overrides/unknown-property-appliedAn override with a property the Ruleset Object does not have makes a linter reject the ruleset for a document it applies to.rejected

Covers /req/overrides/reject-foreign-property. Fixtures in conformance/overrides/unknown-property-applied/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
overrides:
  - files: ["**/*.yaml"]
    foo: bar
    rules:
      r: error

document.yaml

{}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

The linter rejects the ruleset and produces no results.

/req/overrides/reject-unknown-shorthandlinterin section Applying overrides

For a document a file-scoped override applies to, a linter MUST reject the ruleset when a shorthand in the override’s rules names a rule the ruleset neither defines nor inherits.

/conf/shorthands/override-own-ruleA shorthand in an applying override for a rule the ruleset defines itself sets its severity.1 result

Covers /req/overrides/reject-unknown-shorthand. Fixtures in conformance/shorthands/override-own-rule/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
overrides:
  - files: ["**/*.yaml"]
    rules:
      r: error

document.yaml

{}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 0
  }
]
/conf/shorthands/override-unknown-not-appliedA shorthand for an unknown rule in an override that applies to no document being linted does not stop the ruleset from loading.1 result

Covers /req/overrides/reject-unknown-shorthand, /req/overrides/checked-when-applied. Fixtures in conformance/shorthands/override-unknown-not-applied/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
overrides:
  - files: ["legacy/**"]
    rules:
      nope: error

document.yaml

{}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 1
  }
]
/conf/shorthands/override-unknownA shorthand in an override for a rule the ruleset neither defines nor inherits makes a linter reject the ruleset for a document the override applies to.rejected

Covers /req/overrides/reject-unknown-shorthand. Fixtures in conformance/shorthands/override-unknown/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
overrides:
  - files: ["**/*.yaml"]
    rules:
      nope: error

document.yaml

{}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

The linter rejects the ruleset and produces no results.

/req/overrides/reject-undeclared-aliaslinterin section Applying overrides

For a document a file-scoped override applies to, a linter MUST reject the ruleset when a rule the override redefines has an alias reference to an alias that the ruleset defining the rule does not declare.

/conf/overrides/redefined-rule-alias-declaredAn override that redefines a rule with an alias the ruleset declares is loaded, and the redefined rule runs.1 result

Covers /req/overrides/reject-undeclared-alias. Fixtures in conformance/overrides/redefined-rule-alias-declared/.

ruleset.yaml

aliases:
  B:
    - $.info
rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    rules:
      r:
        given: "#B"
        then:
          field: title
          function: truthy

document.yaml

info:
  description: no title

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "info"
    ]
  }
]
/conf/overrides/redefined-rule-alias-appliedAn override that redefines a rule with an alias only the override declares makes a linter reject the ruleset for a document it applies to.rejected

Covers /req/overrides/reject-undeclared-alias. Fixtures in conformance/overrides/redefined-rule-alias-applied/.

ruleset.yaml

rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy
overrides:
  - files: ["**/*.yaml"]
    aliases:
      B:
        - $.info
    rules:
      r:
        given: "#B"
        then:
          function: truthy

document.yaml

info:
  version: "1"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

The linter rejects the ruleset and produces no results.

/req/overrides/checked-when-appliedlinterin section Applying overrides

A linter MUST NOT reject a ruleset for the reasons in the three requirements above when the override applies to no document being linted.

/conf/overrides/unknown-property-not-appliedThe same override, applying to no document being linted, does not stop the ruleset from loading.1 resultalso covers /req/overrides/reject-foreign-property; shown there

/conf/shorthands/override-unknown-not-appliedA shorthand for an unknown rule in an override that applies to no document being linted does not stop the ruleset from loading.1 resultalso covers /req/overrides/reject-unknown-shorthand; shown there

/req/overrides/pointer-scopelinterin section Applying overrides

For a pointer-scoped override, a linter MUST apply the severities only to results whose path is at or below the location the pointer names.

/conf/overrides/pointer-scopeA pointer-scoped override silences the rule under its pointer only.1 result

Covers /req/overrides/pointer-scope. Fixtures in conformance/overrides/pointer-scope/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["document.yaml#/paths/~1legacy"]
    rules:
      acme-operation-summary: "off"

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses: {}
  /legacy:
    get:
      responses: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "path": [
      "paths",
      "/pets",
      "get"
    ]
  }
]
/conf/overrides/pointer-scope-atA pointer that names the result’s own location silences it.1 result

Covers /req/overrides/pointer-scope. Fixtures in conformance/overrides/pointer-scope-at/.

ruleset.yaml

rules:
  r:
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy
overrides:
  - files: ["document.yaml#/paths/~1a/get"]
    rules:
      r: "off"

document.yaml

paths:
  /a:
    get: {}
  /b:
    get: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "paths",
      "/b",
      "get"
    ]
  }
]

/req/severity/numeric-equivalencelinterin section Rule state: enabled and severity

A linter MUST treat each numeric severity as equivalent to the name it is paired with in the table in #rule-severity.

/conf/severity/numeric-equivalenceA rule with severity: 0 reports with the severity of error.2 results

Covers /req/severity/numeric-equivalence. Fixtures in conformance/severity/numeric-equivalence/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: 0
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary",
    "severity": 0
  },
  {
    "code": "acme-operation-summary",
    "severity": 0
  }
]

/req/severity/defaultlinterin section Rule state: enabled and severity

When neither a rule nor any definition it redefines has a severity, a linter MUST use warn.

/conf/extends/redefine-keeps-propertiesA redefinition that sets only given and then keeps the inherited rule’s severity and message.1 result

Covers /req/rules/redefinition-merge, /req/severity/default. Fixtures in conformance/extends/redefine-keeps-properties/.

ruleset.yaml

extends: ./base.yaml
rules:
  r:
    given: $.info
    then:
      field: title
      function: truthy

document.yaml

openapi: 3.1.0
info:
  version: "1"

base.yaml

rules:
  r:
    severity: error
    message: base message
    given: $.nothing
    then:
      function: truthy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 0,
    "message": "base message"
  }
]
/conf/severity/defaultA rule without severity reports with the severity of warn.2 results

Covers /req/severity/default. Fixtures in conformance/severity/default/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary",
    "severity": 1
  },
  {
    "code": "acme-operation-summary",
    "severity": 1
  }
]

/req/rules/recommendedlinterin section Rule state: enabled and severity

A linter MUST enable every rule a ruleset defines in its own rules whose recommended is true or absent, and MUST NOT enable one whose recommended is false unless a shorthand in an extending ruleset enables it.

/conf/rules/recommended-defaultA rule the ruleset defines runs whether recommended is true or absent.2 results

Covers /req/rules/recommended. Fixtures in conformance/rules/recommended-default/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
  ctl:
    given: $
    then:
      function: falsy
    recommended: true

document.yaml

a: 1

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "r"
  },
  {
    "code": "ctl"
  }
]
/conf/rules/recommended-falseA rule the ruleset defines with recommended: false does not run, while its other rules do.1 result

Covers /req/rules/recommended, /req/severity/off. Fixtures in conformance/rules/recommended-false/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy
    recommended: false
  ctl:
    given: $
    then:
      function: falsy

document.yaml

a: 1

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "ctl"
  }
]

/req/extends/mode-alllinterin section Rule state: enabled and severity

When a ruleset is extended with the mode all, a linter MUST enable every rule inherited from it.

/conf/extends/mode-allWith mode all, both rules run.2 results

Covers /req/extends/mode-all. Fixtures in conformance/extends/mode-all/.

ruleset.yaml

extends:
  - [./a.yaml, all]

document.yaml

{}

a.yaml

rules:
  r1:
    description: Not on by default.
    recommended: false
    given: $
    then:
      function: falsy
  r2:
    description: On by default.
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "r1"
  },
  {
    "code": "r2"
  }
]
/conf/extends/mode-all-extra-elementWith [./base.yaml, all, extra], the mode all applies and the extra element is ignored.1 result

Covers /req/extends/mode-all. Fixtures in conformance/extends/mode-all-extra-element/.

ruleset.yaml

extends:
  - [./base.yaml, all, extra]

document.yaml

openapi: 3.1.0
info:
  version: "1"
paths: {}

base.yaml

rules:
  base-rule:
    recommended: false
    given: $.info
    then:
      field: title
      function: truthy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "base-rule"
  }
]

/req/extends/mode-offlinterin section Rule state: enabled and severity

When a ruleset is extended with the mode off, a linter MUST make every rule inherited from it available and MUST NOT enable any of them.

/conf/extends/mode-offWith mode off, no inherited rule runs until it is enabled by name.1 result

Covers /req/extends/mode-off. Fixtures in conformance/extends/mode-off/.

ruleset.yaml

extends:
  - [./a.yaml, "off"]
rules:
  r1: true

document.yaml

{}

a.yaml

rules:
  r1:
    description: Not on by default.
    recommended: false
    given: $
    then:
      function: falsy
  r2:
    description: On by default.
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r1"
  }
]

/req/extends/orderlinterin section Rule state: enabled and severity

A linter MUST apply definitions and adjustments of a rule in this order: the rulesets identified by extends, in array order, then the extending ruleset’s own rules.

/conf/extends/precedenceThe extending ruleset’s definition of r wins over both extended rulesets.1 result

Covers /req/extends/order. Fixtures in conformance/extends/precedence/.

ruleset.yaml

extends:
  - ./a.yaml
  - ./b.yaml
rules:
  r:
    description: from C
    message: "{{description}}"
    given: $
    then:
      function: falsy

document.yaml

{}

a.yaml

rules:
  r:
    description: from A
    message: "{{description}}"
    given: $
    then:
      function: falsy

b.yaml

rules:
  r:
    description: from B
    message: "{{description}}"
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "message": "from C"
  }
]
/conf/extends/precedence-orderOf two extended rulesets defining the same rule, the later one wins.1 result

Covers /req/extends/order. Fixtures in conformance/extends/precedence-order/.

ruleset.yaml

extends:
  - ./a.yaml
  - ./b.yaml

document.yaml

{}

a.yaml

rules:
  r:
    description: from A
    message: "{{description}}"
    given: $
    then:
      function: falsy

b.yaml

rules:
  r:
    description: from B
    message: "{{description}}"
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "message": "from B"
  }
]

/req/rules/redefinition-mergelinterin section Rule state: enabled and severity

When a Rule Object redefines a rule defined earlier in that order, a linter MUST replace each property the Rule Object has and MUST keep every property it does not have.

/conf/extends/redefine-keeps-propertiesA redefinition that sets only given and then keeps the inherited rule’s severity and message.1 resultalso covers /req/severity/default; shown there

/req/shorthands/severitylinterin section Rule state: enabled and severity

For a shorthand that is a severity name, a linter MUST set the rule’s effective severity to it and MUST enable the rule, or not enable it when the name is off.

/conf/shorthands/severityA severity name enables the rule and sets its severity.1 result

Covers /req/shorthands/severity. Fixtures in conformance/shorthands/severity/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: error

document.yaml

{}

a.yaml

rules:
  r:
    recommended: false
    severity: warn
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 0
  }
]

/req/shorthands/falselinterin section Rule state: enabled and severity

For a shorthand that is false, a linter MUST NOT enable the rule and MUST leave its effective severity unchanged.

/conf/shorthands/false-controlWithout the false shorthand, the inherited rule runs.1 result

Covers /req/shorthands/false, /req/severity/off. Fixtures in conformance/shorthands/false-control/.

ruleset.yaml

extends: ./a.yaml

document.yaml

{}

a.yaml

rules:
  r:
    severity: error
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r"
  }
]
/conf/shorthands/falsefalse disables an enabled rule.0 results

Covers /req/shorthands/false, /req/severity/off. Fixtures in conformance/shorthands/false/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: false

document.yaml

{}

a.yaml

rules:
  r:
    recommended: true
    severity: error
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces no results.

/req/shorthands/truelinterin section Rule state: enabled and severity

For a shorthand that is true, a linter MUST enable the rule unless its effective severity is off, and MUST leave the effective severity unchanged, including a severity set by an earlier ruleset in the chain.

/conf/linter/walkthroughThe running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document.3 resultsalso covers /req/extends/load, /req/overrides/scope, /req/formats/ruleset-default, /req/given/alias-expansion, /req/result/severity; shown there

/conf/shorthands/true-enablestrue enables a rule that is off by default, at the severity it was defined with.1 result

Covers /req/shorthands/true. Fixtures in conformance/shorthands/true-enables/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: true

document.yaml

{}

a.yaml

rules:
  r:
    recommended: false
    severity: error
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 0
  }
]
/conf/shorthands/true-keeps-severityA rule disabled with false in one ruleset and re-enabled with true in the next keeps the severity it was defined with.1 result

Covers /req/shorthands/true. Fixtures in conformance/shorthands/true-keeps-severity/.

ruleset.yaml

extends: ./b.yaml
rules:
  r: true

document.yaml

{}

a.yaml

rules:
  r:
    recommended: true
    severity: error
    given: $
    then:
      function: falsy

b.yaml

extends: ./a.yaml
rules:
  r: false

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "severity": 0
  }
]
/conf/shorthands/true-offtrue on a rule whose severity is off does not enable it.0 results

Covers /req/shorthands/true. Fixtures in conformance/shorthands/true-off/.

ruleset.yaml

extends: ./a.yaml
rules:
  r: true

document.yaml

{}

a.yaml

rules:
  r:
    severity: "off"
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces no results.

/req/severity/offlinterin section Rule state: enabled and severity

A linter MUST NOT evaluate a rule that is not enabled, whether its effective severity is off, a shorthand is false, or its recommended is false and nothing enables it, and MUST NOT produce results for it.

/conf/rules/recommended-falseA rule the ruleset defines with recommended: false does not run, while its other rules do.1 resultalso covers /req/rules/recommended; shown there

/conf/severity/off-controlThe same rule at warn produces the results that off suppresses.2 results

Covers /req/severity/off. Fixtures in conformance/severity/off-control/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: warn
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary"
  },
  {
    "code": "acme-operation-summary"
  }
]

/conf/shorthands/false-controlWithout the false shorthand, the inherited rule runs.1 resultalso covers /req/shorthands/false; shown there

/conf/severity/offA rule with severity: off produces no results although it would fail.0 results

Covers /req/severity/off. Fixtures in conformance/severity/off/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: off
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces no results.

/conf/shorthands/falsefalse disables an enabled rule.0 resultsalso covers /req/shorthands/false; shown there

/req/formats/identifierslinterin section Selecting documents

A linter MUST detect a format for a document when the document’s root is an object and the condition the table in #formats gives for the identifier holds, and MUST NOT detect it otherwise, among the identifiers /req/formats/detection-scope lets it detect.

/conf/formats/detect-aas2-6A document with asyncapi: 2.6.0 is aas2_6 and not aas2_5.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-aas2-6/.

ruleset.yaml

rules:
  is-aas2_6:
    formats: [aas2_6]
    given: $
    then:
      function: falsy
  is-aas2_5:
    formats: [aas2_5]
    given: $
    then:
      function: falsy

document.yaml

asyncapi: 2.6.0

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-aas2_6"
  }
]
/conf/formats/detect-aas2-needs-patchasyncapi: "2.6" without a patch number is not aas2, while 2.6.0 is.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-aas2-needs-patch/.

ruleset.yaml

rules:
  is-aas2:
    formats: [aas2]
    given: $
    then:
      function: falsy
  is-aas3:
    formats: [aas3]
    given: $
    then:
      function: falsy

document.yaml

asyncapi: 2.6.0

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-aas2"
  }
]
/conf/formats/detect-arazzoA document with arazzo: 1.0.1 is arazzo1_0.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-arazzo/.

ruleset.yaml

rules:
  is-arazzo1_0:
    formats: [arazzo1_0]
    given: $
    then:
      function: falsy
  is-oas3:
    formats: [oas3]
    given: $
    then:
      function: falsy

document.yaml

arazzo: 1.0.1

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-arazzo1_0"
  }
]
/conf/formats/detect-json-schema-2020-12A $schema of draft 2020-12 is jsonSchemaDraft2020_12 and not draft 7.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-json-schema-2020-12/.

ruleset.yaml

rules:
  is-jsonSchemaDraft2020_12:
    formats: [jsonSchemaDraft2020_12]
    given: $
    then:
      function: falsy
  is-jsonSchemaDraft7:
    formats: [jsonSchemaDraft7]
    given: $
    then:
      function: falsy

document.yaml

$schema: https://json-schema.org/draft/2020-12/schema
type: object

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-jsonSchemaDraft2020_12"
  }
]
/conf/formats/detect-json-schema-looseA document with type: object and no $schema is jsonSchemaLoose but not jsonSchema.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-json-schema-loose/.

ruleset.yaml

rules:
  is-jsonSchemaLoose:
    formats: [jsonSchemaLoose]
    given: $
    then:
      function: falsy
  is-jsonSchema:
    formats: [jsonSchema]
    given: $
    then:
      function: falsy

document.yaml

type: object

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-jsonSchemaLoose"
  }
]
/conf/formats/detect-number-versionAn unquoted openapi: 3.0 is the number 3, whose string form is 3, so the document is oas3 but not oas3_0.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-number-version/.

ruleset.yaml

rules:
  is-oas3:
    formats: [oas3]
    given: $
    then:
      function: falsy
  is-oas3_0:
    formats: [oas3_0]
    given: $
    then:
      function: falsy

document.yaml

openapi: 3.0

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-oas3"
  }
]
/conf/formats/detect-oas2A document with swagger: "2.0" is oas2 and not oas3.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-oas2/.

ruleset.yaml

rules:
  is-oas2:
    formats: [oas2]
    given: $
    then:
      function: falsy
  is-oas3:
    formats: [oas3]
    given: $
    then:
      function: falsy

document.yaml

swagger: "2.0"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-oas2"
  }
]
/conf/formats/detect-oas3-1An OpenAPI 3.1 document is detected as oas3_1 and not as oas3_0.1 result

Covers /req/formats/identifiers. Fixtures in conformance/formats/detect-oas3-1/.

ruleset.yaml

rules:
  is-oas3-1:
    formats: [oas3_1]
    given: $
    then:
      function: falsy
  is-oas3-0:
    formats: [oas3_0]
    given: $
    then:
      function: falsy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "is-oas3-1"
  }
]

/req/formats/detection-scopelinterin section Selecting documents

A linter MUST detect a document’s formats only among the identifiers named in the formats of the ruleset, of its rules, and of the rulesets it extends and their rules. An identifier named only by a target of a scoped alias is never detected.

/conf/formats/detection-declared-onlyoas3_1 is not detected when no rule names it, so for an OpenAPI 3.1 document the alias uses its oas3 target, though an oas3_1 target comes last.1 result

Covers /req/formats/detection-scope. Fixtures in conformance/formats/detection-declared-only/.

ruleset.yaml

aliases:
  I:
    targets:
      - formats: [oas3]
        given: ["$.info"]
      - formats: [oas3_1]
        given: ["$.paths"]
rules:
  r:
    formats: [oas3]
    given: "#I"
    then:
      field: title
      function: truthy

document.yaml

openapi: 3.1.0
info:
  version: "1"
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "info"
    ]
  }
]

/req/formats/rule-scopelinterin section Selecting documents

A linter MUST evaluate a rule that has formats only against documents that have at least one of those formats.

/conf/formats/rule-scopeOf two rules scoped to different formats, only the one whose format the document has runs.1 result

Covers /req/formats/rule-scope. Fixtures in conformance/formats/rule-scope/.

ruleset.yaml

rules:
  only-oas3:
    formats: [oas3]
    given: $
    then:
      function: falsy
  only-oas2:
    formats: [oas2]
    given: $
    then:
      function: falsy

document.yaml

swagger: "2.0"
info:
  title: Petstore
  version: 1.0.0
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "only-oas2"
  }
]

/req/formats/ruleset-defaultlinterin section Selecting documents

A linter MUST treat a rule without formats, defined in a ruleset with formats, as having the formats of the ruleset that defines it, also when another ruleset extends it.

/conf/formats/ruleset-defaultA rule without formats takes the ruleset’s, so it does not run on a document of another format, while a rule scoped to that format does.1 result

Covers /req/formats/ruleset-default. Fixtures in conformance/formats/ruleset-default/.

ruleset.yaml

formats: [oas3]
rules:
  r:
    given: $
    then:
      function: falsy
  s:
    formats: [oas2]
    given: $
    then:
      function: falsy

document.yaml

swagger: "2.0"
info:
  title: Petstore
  version: 1.0.0
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "s"
  }
]
/conf/formats/unrecognized-unscopedA document with none of the ruleset’s formats gets the warning, the ruleset’s own rule does not run, and an unscoped rule from an extended ruleset does.2 results

Covers /req/formats/unrecognized-document, /req/formats/ruleset-default. Fixtures in conformance/formats/unrecognized-unscoped/.

ruleset.yaml

extends: ./base.yaml
formats: [oas3]
rules:
  r:
    given: $
    then:
      function: falsy

document.yaml

asyncapi: 2.6.0
info:
  title: Events
  version: 1.0.0
channels: {}

base.yaml

rules:
  s:
    given: $
    then:
      function: falsy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "unrecognized-format",
    "severity": 1
  },
  {
    "code": "s"
  }
]

/conf/linter/walkthroughThe running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document.3 resultsalso covers /req/extends/load, /req/overrides/scope, /req/shorthands/true, /req/given/alias-expansion, /req/result/severity; shown there

/req/formats/rule-overrides-rulesetlinterin section Selecting documents

When both a rule and its ruleset have formats, a linter MUST use the rule’s and ignore the ruleset’s for that rule.

/conf/formats/rule-overrides-rulesetA rule’s own formats replaces the ruleset’s.1 result

Covers /req/formats/rule-overrides-ruleset. Fixtures in conformance/formats/rule-overrides-ruleset/.

ruleset.yaml

formats: [oas3]
rules:
  r:
    formats: [oas2]
    given: $
    then:
      function: falsy

document.yaml

swagger: "2.0"
info:
  title: Petstore
  version: 1.0.0
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r"
  }
]

/req/formats/unscoped-runs-everywherelinterin section Selecting documents

A linter MUST evaluate a rule that has no formats, in a ruleset that has none, against every document, whatever its formats.

/conf/formats/unscopedWithout formats anywhere, a rule runs on a document of no known format.1 result

Covers /req/formats/unscoped-runs-everywhere. Fixtures in conformance/formats/unscoped/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      function: falsy

document.yaml

hello: world

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r"
  }
]

/req/formats/unrecognized-documentlinterin section Selecting documents

When a ruleset has formats, directly or through its rules, and a document has none of them, a linter MUST produce one result with the code unrecognized-format and the severity warn, and MUST still run every rule that has no formats of its own and inherits none from the ruleset.

/conf/formats/unrecognized-documentA document that has none of the ruleset’s formats gets one warning and no rule results.1 result

Covers /req/formats/unrecognized-document. Fixtures in conformance/formats/unrecognized-document/.

ruleset.yaml

formats: [oas3]
rules:
  r:
    given: $
    then:
      function: falsy

document.yaml

swagger: "2.0"
info:
  title: Petstore
  version: 1.0.0
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "unrecognized-format",
    "severity": 1
  }
]

/conf/formats/unrecognized-unscopedA document with none of the ruleset’s formats gets the warning, the ruleset’s own rule does not run, and an unscoped rule from an extended ruleset does.2 resultsalso covers /req/formats/ruleset-default; shown there

/req/given/evaluatelinterin section Selecting nodes

For every rule that is enabled for a document, a linter MUST evaluate each path expression in given against the document and apply the rule’s then to every matched node.

/conf/given/evaluate-all-matchesTwo operations without summary produce one result each.2 results

Covers /req/given/evaluate. Fixtures in conformance/given/evaluate-all-matches/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary",
    "path": [
      "paths",
      "/pets",
      "get"
    ]
  },
  {
    "code": "acme-operation-summary",
    "path": [
      "paths",
      "/pets",
      "post"
    ]
  }
]
/conf/given/evaluate-multiple-expressionsTwo path expressions in given each produce their results.2 results

Covers /req/given/evaluate. Fixtures in conformance/given/evaluate-multiple-expressions/.

ruleset.yaml

rules:
  info-filled:
    given:
      - $.info.title
      - $.info.description
    then:
      function: truthy

document.yaml

info:
  title: ""
  description: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "info-filled",
    "path": [
      "info",
      "title"
    ]
  },
  {
    "code": "info-filled",
    "path": [
      "info",
      "description"
    ]
  }
]

/req/given/no-matchlinterin section Selecting nodes

A linter MUST NOT produce a result for a rule whose given matches no node in the document.

/conf/given/matchThe same rule on a document where given matches produces results.1 result

Covers /req/given/no-match. Fixtures in conformance/given/match/.

ruleset.yaml

rules:
  no-paths:
    given: $.paths[*]
    then:
      function: falsy

document.yaml

paths:
  /pets:
    get: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "no-paths"
  }
]
/conf/given/no-matchA rule whose given matches nothing produces no results, even though its function would fail on any node.0 results

Covers /req/given/no-match. Fixtures in conformance/given/no-match/.

ruleset.yaml

rules:
  no-paths:
    given: $.paths[*]
    then:
      function: falsy

document.yaml

info:
  title: No paths here

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces no results.

/req/given/resolvedlinterin section Selecting nodes

A linter MUST evaluate given against the resolved document, unless the rule sets resolved to false, in which case it MUST evaluate given against the document as written.

/conf/given/resolved-falseWith resolved: false, given sees the $ref object, which has no type.1 result

Covers /req/given/resolved. Fixtures in conformance/given/resolved-false/.

ruleset.yaml

rules:
  id-has-type:
    resolved: false
    given: $.components.schemas.Pet.properties.id
    then:
      field: type
      function: defined

document.yaml

components:
  schemas:
    Id:
      type: integer
    Pet:
      properties:
        id:
          $ref: "#/components/schemas/Id"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "id-has-type"
  }
]
/conf/given/resolved-defaultBy default given sees the resolved document, where the $ref has been replaced by a schema with type.0 results

Covers /req/given/resolved. Fixtures in conformance/given/resolved-default/.

ruleset.yaml

rules:
  id-has-type:
    given: $.components.schemas.Pet.properties.id
    then:
      field: type
      function: defined

document.yaml

components:
  schemas:
    Id:
      type: integer
    Pet:
      properties:
        id:
          $ref: "#/components/schemas/Id"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces no results.

/req/given/jsonpathlinterin section Selecting nodes

A linter MUST evaluate a JSONPath expression as JSONPath-Plus 10.4.0 JSONPATH-PLUS evaluates it, including its filter expressions in parentheses, such as $..parameters[?(@.in === "header")].

/conf/given/jsonpath-filterA filter expression in parentheses, ?(@.in === "header"), selects the header parameter with an empty name; the index in its path is a string.1 result

Covers /req/given/jsonpath. Fixtures in conformance/given/jsonpath-filter/.

ruleset.yaml

rules:
  param-name:
    given: '$.parameters[?(@.in === "header")].name'
    then:
      function: truthy

document.yaml

parameters:
  - in: query
    name: ""
  - in: header
    name: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "param-name",
    "path": [
      "parameters",
      "1",
      "name"
    ]
  }
]

/req/given/once-per-nodelinterin section Selecting nodes

A linter MUST apply a rule’s then to a node once, even when several of the rule’s path expressions match it.

/conf/given/once-per-nodeA node matched by three path expressions of one rule is checked once.1 result

Covers /req/given/once-per-node. Fixtures in conformance/given/once-per-node/.

ruleset.yaml

rules:
  r:
    given:
      - $.info
      - $..info
      - "$['info']"
    then:
      field: title
      function: truthy

document.yaml

openapi: 3.1.0
info:
  version: "1"
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "info"
    ]
  }
]

/req/given/alias-expansionlinterin section Selecting nodes

A linter MUST evaluate an alias reference as the set of the named alias’s path expressions, each with the remainder of the reference after the alias name appended to it.

/conf/given/alias-expansion#Info.description is evaluated as $..info.description.1 result

Covers /req/given/alias-expansion. Fixtures in conformance/given/alias-expansion/.

ruleset.yaml

aliases:
  Info:
    - $..info
rules:
  info-description:
    given: "#Info.description"
    then:
      function: truthy

document.yaml

info:
  title: Petstore
  description: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "info-description",
    "path": [
      "info",
      "description"
    ]
  }
]

/conf/linter/walkthroughThe running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document.3 resultsalso covers /req/extends/load, /req/overrides/scope, /req/shorthands/true, /req/formats/ruleset-default, /req/result/severity; shown there

/req/aliases/composelinterin section Selecting nodes

When an alias’s path expressions contain alias references, a linter MUST expand those references in turn, until no reference remains.

/conf/aliases/composeAn alias defined through another alias expands to the full path.1 result

Covers /req/aliases/compose. Fixtures in conformance/aliases/compose/.

ruleset.yaml

aliases:
  Info:
    - $..info
  InfoDescription:
    - "#Info.description"
rules:
  info-description:
    given: "#InfoDescription"
    then:
      function: truthy

document.yaml

info:
  title: Petstore
  description: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "info-description",
    "path": [
      "info",
      "description"
    ]
  }
]

/req/aliases/scopedlinterin section Selecting nodes

For a scoped alias, a linter MUST use the given of the last target whose formats include a format of the document, and MUST match no node with it when no target does.

/conf/aliases/scoped-last-targetFor an OpenAPI 3.1 document, which has both oas3 and oas3_1, only the last matching target is used.1 result

Covers /req/aliases/scoped. Fixtures in conformance/aliases/scoped-last-target/.

ruleset.yaml

aliases:
  I:
    targets:
      - formats: [oas3_1]
        given: ["$.paths"]
      - formats: [oas3]
        given: ["$.info"]
rules:
  r:
    formats: [oas3, oas3_1]
    given: "#I"
    then:
      field: title
      function: truthy

document.yaml

openapi: 3.1.0
info:
  version: "1"
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "info"
    ]
  }
]
/conf/aliases/scoped-no-targetA scoped alias with no target for the document’s formats matches nothing; only the format warning is reported.1 result

Covers /req/aliases/scoped. Fixtures in conformance/aliases/scoped-no-target/.

ruleset.yaml

aliases:
  I:
    targets:
      - formats: [oas2]
        given: ["$.info"]
rules:
  swagger-version:
    formats: [oas2]
    given: $.swagger
    then:
      function: truthy
  r:
    given: "#I"
    then:
      field: title
      function: truthy

document.yaml

openapi: 3.1.0
info:
  version: "1"
paths: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "unrecognized-format"
  }
]
/conf/aliases/scoped-oas2On an OpenAPI 2 document, the scoped alias uses the oas2 target.1 result

Covers /req/aliases/scoped. Fixtures in conformance/aliases/scoped-oas2/.

ruleset.yaml

aliases:
  ParameterObject:
    targets:
      - formats: [oas2]
        given:
          - $.parameters[*]
      - formats: [oas3]
        given:
          - $.components.parameters[*]
rules:
  parameter-name:
    formats: [oas2, oas3]
    given: "#ParameterObject"
    then:
      field: name
      function: truthy

document.yaml

swagger: "2.0"
info:
  title: Petstore
  version: 1.0.0
paths: {}
parameters:
  limit:
    name: ""
    in: query

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "parameter-name",
    "path": [
      "parameters",
      "limit",
      "name"
    ]
  }
]
/conf/aliases/scoped-oas3On an OpenAPI 3 document, the scoped alias uses the oas3 target.1 result

Covers /req/aliases/scoped. Fixtures in conformance/aliases/scoped-oas3/.

ruleset.yaml

aliases:
  ParameterObject:
    targets:
      - formats: [oas2]
        given:
          - $.parameters[*]
      - formats: [oas3]
        given:
          - $.components.parameters[*]
rules:
  parameter-name:
    formats: [oas2, oas3]
    given: "#ParameterObject"
    then:
      field: name
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths: {}
components:
  parameters:
    limit:
      name: ""
      in: query

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "parameter-name",
    "path": [
      "components",
      "parameters",
      "limit",
      "name"
    ]
  }
]

/req/then/apply-eachlinterin section Applying functions

For each matched node, a linter MUST apply every function invocation in then, and MUST report each problem a function returns as a result.

/conf/then/apply-eachTwo invocations that both fail on the same node produce two results.2 results

Covers /req/then/apply-each. Fixtures in conformance/then/apply-each/.

ruleset.yaml

rules:
  title-checks:
    given: $.info.title
    then:
      - function: truthy
      - function: length
        functionOptions:
          min: 1

document.yaml

info:
  title: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "title-checks"
  },
  {
    "code": "title-checks"
  }
]
/conf/then/apply-each-problemOne invocation whose function returns two problems produces two results.2 results

Covers /req/then/apply-each. Fixtures in conformance/then/apply-each-problem/.

ruleset.yaml

rules:
  r:
    given: $.o
    then:
      function: schema
      functionOptions:
        allErrors: true
        schema:
          type: object
          required: [a, b]

document.yaml

o: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "r"
  },
  {
    "code": "r"
  }
]

/req/then/fieldlinterin section Applying functions

When an invocation has field, a linter MUST apply the function to each value field selects in the matched node: the property it names, the value at the path of property names and indices it names, or every value its JSONPath expression selects when it starts with $. When field selects nothing, the linter MUST apply the function once, to no value, and a result MUST have the path and range of the matched node. A result for a selected value MUST have the path of that value.

/conf/then/fieldWith field, the function sees the named property of the matched node.1 result

Covers /req/then/field. Fixtures in conformance/then/field/.

ruleset.yaml

rules:
  info-title:
    given: $.info
    then:
      field: title
      function: truthy

document.yaml

info:
  title: ""
  version: 1.0.0

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "info-title",
    "path": [
      "info",
      "title"
    ]
  }
]
/conf/then/field-jsonpathA field that is a JSONPath expression applies the function to every value it selects.2 results

Covers /req/then/field. Fixtures in conformance/then/field-jsonpath/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      field: "$.items[*].name"
      function: truthy

document.yaml

items:
  - name: ""
  - name: ok
  - name: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "r"
  },
  {
    "code": "r"
  }
]
/conf/then/field-missingWith field naming a property the node lacks, the function sees no value, so defined fails.1 result

Covers /req/then/field. Fixtures in conformance/then/field-missing/.

ruleset.yaml

rules:
  info-description:
    given: $.info
    then:
      field: description
      function: defined

document.yaml

info:
  title: Petstore

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "info-description",
    "path": [
      "info"
    ]
  }
]
/conf/then/field-pathA field that is a path of property names reaches a nested value.1 result

Covers /req/then/field. Fixtures in conformance/then/field-path/.

ruleset.yaml

rules:
  r:
    given: $
    then:
      field: info.contact.email
      function: truthy

document.yaml

info:
  contact:
    email: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "info",
      "contact",
      "email"
    ]
  }
]

/req/then/field-keylinterin section Applying functions

When field is @key, a linter MUST apply the function once to each property key of the matched node, and a result for a key MUST have the path of that property.

/conf/then/field-keyWith field: "@key" on a matched object, the function sees each of its property keys; the key that does not match the pattern is reported.1 result

Covers /req/then/field-key. Fixtures in conformance/then/field-key/.

ruleset.yaml

rules:
  path-kebab:
    given: $.paths
    then:
      field: "@key"
      function: pattern
      functionOptions:
        match: "^/[a-z-]+$"

document.yaml

paths:
  /pets: {}
  /Pets_old: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "path-kebab",
    "path": [
      "paths",
      "/Pets_old"
    ]
  }
]

/req/then/function-optionslinterin section Applying functions

A linter MUST pass the invocation’s functionOptions to the function.

/conf/then/function-optionsThe max option reaches the length function.1 result

Covers /req/then/function-options. Fixtures in conformance/then/function-options/.

ruleset.yaml

rules:
  short-title:
    given: $.info.title
    then:
      function: length
      functionOptions:
        max: 3

document.yaml

info:
  title: Petstore

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "short-title",
    "path": [
      "info",
      "title"
    ]
  }
]

/req/result/fieldslinterin section Results

Every result a linter produces MUST have code, path, severity, message, and range.

/conf/result/fieldsEvery result has the five required fields.2 results

Covers /req/result/fields. Fixtures in conformance/result/fields/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "has": [
      "code",
      "path",
      "severity",
      "message",
      "range"
    ]
  },
  {
    "has": [
      "code",
      "path",
      "severity",
      "message",
      "range"
    ]
  }
]

/req/result/codelinterin section Results

The code of a result a rule produces MUST be its rule name. The code of a result the linter produces itself MUST be one of those in the table below.

/conf/result/codeThe code of a result is the rule name.2 results

Covers /req/result/code. Fixtures in conformance/result/code/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary"
  },
  {
    "code": "acme-operation-summary"
  }
]

/req/result/severitylinterin section Results

The severity of a result MUST be the numeric form of the rule’s effective severity.

/conf/linter/walkthroughThe running example of the Linting chapter: a ruleset that extends a base, enables a rule by shorthand, declares an alias, and overrides a severity for legacy documents, linting one legacy document.3 resultsalso covers /req/extends/load, /req/overrides/scope, /req/shorthands/true, /req/formats/ruleset-default, /req/given/alias-expansion; shown there

/conf/result/severityA rule with severity: hint reports severity: 3.2 results

Covers /req/result/severity. Fixtures in conformance/result/severity/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: hint
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "severity": 3
  },
  {
    "severity": 3
  }
]
/conf/severity/resultA rule inherited at warn and raised to error reports with the severity of error.2 results

Covers /req/result/severity. Fixtures in conformance/severity/result/.

ruleset.yaml

extends: ./base.yaml
rules:
  acme-operation-summary: error

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

base.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    severity: warn
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary",
    "severity": 0
  },
  {
    "code": "acme-operation-summary",
    "severity": 0
  }
]

/req/result/rangelinterin section Results

The range of a result MUST locate the value the result is about in the source text of the document: start at its first character and end just after its last, each with a zero-based line and a zero-based character counted in UTF-16 code units.

/conf/result/rangeA result about the root value starts at line 0, character 0.1 result

Covers /req/result/range. Fixtures in conformance/result/range/.

ruleset.yaml

rules:
  root-falsy:
    given: $
    then:
      function: falsy

document.yaml

info:
  title: Petstore

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "root-falsy",
    "range": {
      "start": {
        "line": 0,
        "character": 0
      }
    }
  }
]
/conf/result/range-utf16A character before the value that needs two UTF-16 code units counts as two, and the end is just after the value.1 result

Covers /req/result/range. Fixtures in conformance/result/range-utf16/.

ruleset.yaml

rules:
  b:
    given: $.b
    then:
      function: truthy

document.json

{"a": "😀", "b": ""}

Procedure

Lint document.json with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "b",
    "range": {
      "start": {
        "line": 0,
        "character": 17
      },
      "end": {
        "line": 0,
        "character": 19
      }
    }
  }
]

/req/result/sourcelinterin section Results

When the document was read from a file path or URL, every result for it MUST carry that location in source, as an absolute path or the URL.

/conf/result/sourceA result for a document read from a file carries that file’s path in source.2 results

Covers /req/result/source. Fixtures in conformance/result/source/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "acme-operation-summary",
    "source": "${document}"
  },
  {
    "code": "acme-operation-summary",
    "source": "${document}"
  }
]

/req/result/orderlinterin section Results

A linter MUST list results in the order of the documents they belong to, as the documents were given, and within a document by the line and then the character of range.start, and then by code.

/conf/result/orderResults come in order of position, and by code at the same position, whatever the order of the rules.5 results

Covers /req/result/order. Fixtures in conformance/result/order/.

ruleset.yaml

rules:
  zzz:
    given: [$.b, $.d]
    then:
      function: falsy
  aaa:
    given: [$.a, $.c]
    then:
      function: falsy
  mmm:
    given: $.a
    then:
      function: falsy

document.yaml

a: 1
b: 1
c: 1
d: 1

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 5 results, matching on the fields given, in any order:

[
  {
    "code": "aaa",
    "path": [
      "a"
    ]
  },
  {
    "code": "mmm",
    "path": [
      "a"
    ]
  },
  {
    "code": "zzz",
    "path": [
      "b"
    ]
  },
  {
    "code": "aaa",
    "path": [
      "c"
    ]
  },
  {
    "code": "zzz",
    "path": [
      "d"
    ]
  }
]

/req/result/reference-targetlinterin section Results

When a rule ran against the resolved document and the value a result is about was reached through a reference, the result MUST carry the path, range, and source of the value where it is written, and a value reached through several references MUST produce one result.

/conf/resolution/shared-referenceA schema referenced from two operations fails a rule once, at the place where the schema is written.1 result

Covers /req/result/reference-target. Fixtures in conformance/resolution/shared-reference/.

ruleset.yaml

rules:
  no-object:
    given: $.paths..schema.type
    then:
      function: pattern
      functionOptions:
        notMatch: "object"

document.yaml

openapi: 3.1.0
info:
  title: Shared
  version: 1.0.0
paths:
  /a:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Bad'
  /b:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Bad'
components:
  schemas:
    Bad:
      type: object

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "no-object",
    "path": [
      "components",
      "schemas",
      "Bad",
      "type"
    ]
  }
]

/req/message/overridelinterin section The message

When a rule has message, the message of every result it produces MUST be the rule’s message with each placeholder substituted.

/conf/message/overrideA rule’s message replaces the reported message.2 results

Covers /req/message/override. Fixtures in conformance/message/override/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    message: Summary missing here
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "message": "Summary missing here"
  },
  {
    "message": "Summary missing here"
  }
]

/req/message/placeholderslinterin section The message

A linter MUST substitute {{error}} with the message the function produced, {{description}} with the rule’s description, {{path}} with the path to the offending value as a JSON Pointer RFC6901 with a leading #, {{property}} with the last key or index of that path, and {{value}} with the offending value.

/conf/message/placeholder-description{{description}} resolves to the rule’s description.2 results

Covers /req/message/placeholders. Fixtures in conformance/message/placeholder-description/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    message: "{{description}}"
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "message": "Every operation needs a summary."
  },
  {
    "message": "Every operation needs a summary."
  }
]
/conf/message/placeholder-path{{path}} is the JSON Pointer to the offending value, with a leading #.1 result

Covers /req/message/placeholders. Fixtures in conformance/message/placeholder-path/.

ruleset.yaml

rules:
  r:
    message: "{{path}}"
    given: $.info.title
    then:
      function: falsy

document.yaml

info:
  title: x

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "message": "#/info/title"
  }
]
/conf/message/placeholder-property{{property}} resolves to the key of the offending value.1 result

Covers /req/message/placeholders. Fixtures in conformance/message/placeholder-property/.

ruleset.yaml

rules:
  info-title:
    message: "{{property}}"
    given: $.info
    then:
      field: title
      function: truthy

document.yaml

info:
  title: ""

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "message": "title"
  }
]

/req/message/defaultlinterin section The message

When a rule has no message, the message of every result it produces MUST be the rule’s description, or the message the function produced when the rule has no description.

/conf/message/default-descriptionWithout message, the result carries the rule’s description.2 results

Covers /req/message/default. Fixtures in conformance/message/default-description/.

ruleset.yaml

rules:
  acme-operation-summary:
    description: Every operation needs a summary.
    given: $.paths[*]['get','put','post','delete','options','head','patch','trace']
    then:
      field: summary
      function: truthy

document.yaml

openapi: 3.1.0
info:
  title: Petstore
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        "200":
          description: OK
    post:
      responses:
        "201":
          description: Created

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "message": "Every operation needs a summary."
  },
  {
    "message": "Every operation needs a summary."
  }
]

/req/functions/input-typeslinterin section Core functions

A core function MUST pass when the value it is applied to is absent or of a type the function does not apply to, unless its requirement below says otherwise.

/conf/functions/input-typespattern passes on a number and on an absent value, and fails only on the string that does not match; xor passes on a string.1 result

Covers /req/functions/input-types. Fixtures in conformance/functions/input-types/.

ruleset.yaml

rules:
  starts-with-a:
    given: $.*
    then:
      function: pattern
      functionOptions:
        match: "^a"
  absent:
    given: $
    then:
      field: missing
      function: pattern
      functionOptions:
        match: "^a"
  one-of:
    given: $.s
    then:
      function: xor
      functionOptions:
        properties: [p, q]

document.yaml

n: 5
s: bcd
t: abc

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "starts-with-a",
    "path": [
      "s"
    ]
  }
]

/req/functions/truthylinterin section truthy

truthy MUST fail when the value is absent, false, "", 0, or null, and MUST pass otherwise, including for an empty array and an empty object.

/conf/functions/truthytruthy fails on false, "", 0, and null, and passes on an empty array, an empty object, and a string.4 results

Covers /req/functions/truthy. Fixtures in conformance/functions/truthy/.

ruleset.yaml

rules:
  all-truthy:
    given: $.*
    then:
      function: truthy

document.yaml

a: false
b: ""
c: 0
d: null
e: []
f: {}
g: x

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 4 results, matching on the fields given, in any order:

[
  {
    "path": [
      "a"
    ]
  },
  {
    "path": [
      "b"
    ]
  },
  {
    "path": [
      "c"
    ]
  },
  {
    "path": [
      "d"
    ]
  }
]

/req/functions/falsylinterin section falsy

falsy MUST fail when the value is present and not one of false, "", 0, or null, and MUST pass otherwise.

/conf/functions/falsyfalsy fails on an empty array, an empty object, and a string, and passes on false, "", 0, and null.3 results

Covers /req/functions/falsy. Fixtures in conformance/functions/falsy/.

ruleset.yaml

rules:
  all-falsy:
    given: $.*
    then:
      function: falsy

document.yaml

a: false
b: ""
c: 0
d: null
e: []
f: {}
g: x

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 3 results, matching on the fields given, in any order:

[
  {
    "path": [
      "e"
    ]
  },
  {
    "path": [
      "f"
    ]
  },
  {
    "path": [
      "g"
    ]
  }
]

/req/functions/definedlinterin section defined

defined MUST fail when the value is absent and MUST pass when it is present, whatever it is.

/conf/functions/defineddefined fails on an absent property and passes on a present one.1 result

Covers /req/functions/defined. Fixtures in conformance/functions/defined/.

ruleset.yaml

rules:
  has-title:
    given: $.info
    then:
      field: title
      function: defined
  has-description:
    given: $.info
    then:
      field: description
      function: defined

document.yaml

info:
  title: Petstore

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "has-description"
  }
]

/req/functions/undefinedlinterin section undefined

undefined MUST fail when the value is present and MUST pass when it is absent.

/conf/functions/undefinedundefined fails on a present property and passes on an absent one.1 result

Covers /req/functions/undefined. Fixtures in conformance/functions/undefined/.

ruleset.yaml

rules:
  no-title:
    given: $.info
    then:
      field: title
      function: undefined
  no-description:
    given: $.info
    then:
      field: description
      function: undefined

document.yaml

info:
  title: Petstore

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "no-title"
  }
]

/req/functions/patternlinterin section pattern

pattern MUST fail when the option match is given and the value does not match it, or when the option notMatch is given and the value matches it. Both options are regular expressions as defined by ECMAScript ECMA-262-2025.

/conf/functions/patternmatch fails on a string that does not match, notMatch fails on one that does.2 results

Covers /req/functions/pattern. Fixtures in conformance/functions/pattern/.

ruleset.yaml

rules:
  must-match:
    given: $.*
    then:
      function: pattern
      functionOptions:
        match: "^[a-z]+$"
  must-not-match:
    given: $.*
    then:
      function: pattern
      functionOptions:
        notMatch: "^abc$"

document.yaml

a: abc
b: ABC

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "must-match",
    "path": [
      "b"
    ]
  },
  {
    "code": "must-not-match",
    "path": [
      "a"
    ]
  }
]

/req/functions/casinglinterin section casing

casing MUST fail when the value is a non-empty string that is not written in the case named by the option type: one of flat, camel, pascal, kebab, cobol, snake, and macro, where only ASCII letters and digits count as written in a case.

/conf/functions/casingcasing with type: kebab fails on a camel-case string.1 result

Covers /req/functions/casing. Fixtures in conformance/functions/casing/.

ruleset.yaml

rules:
  kebab:
    given: $.names[*]
    then:
      function: casing
      functionOptions:
        type: kebab

document.yaml

names:
  - kebab-case
  - camelCase

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "kebab"
  }
]
/conf/functions/casing-non-asciiOnly ASCII letters count, so éclair is not camel case; the empty string passes.1 result

Covers /req/functions/casing. Fixtures in conformance/functions/casing-non-ascii/.

ruleset.yaml

rules:
  r:
    given: [$.a, $.b, $.c]
    then:
      function: casing
      functionOptions:
        type: "camel"

document.yaml

a: éclair
b: ""
c: fooBar

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "a"
    ]
  }
]
/conf/functions/casing-typesEach case accepts a value written in it and rejects one written in another case.6 results

Covers /req/functions/casing. Fixtures in conformance/functions/casing-types/.

ruleset.yaml

rules:
  camel:
    given: $.camel[*]
    then:
      function: casing
      functionOptions:
        type: camel
  pascal:
    given: $.pascal[*]
    then:
      function: casing
      functionOptions:
        type: pascal
  snake:
    given: $.snake[*]
    then:
      function: casing
      functionOptions:
        type: snake
  macro:
    given: $.macro[*]
    then:
      function: casing
      functionOptions:
        type: macro
  cobol:
    given: $.cobol[*]
    then:
      function: casing
      functionOptions:
        type: cobol
  flat:
    given: $.flat[*]
    then:
      function: casing
      functionOptions:
        type: flat

document.yaml

camel: [fooBar, foo_bar]
pascal: [FooBar, fooBar]
snake: [foo_bar, foo-bar]
macro: [FOO_BAR, foo_bar]
cobol: [FOO-BAR, foo-bar]
flat: [foobar, fooBar]

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 6 results, matching on the fields given, in any order:

[
  {
    "code": "camel"
  },
  {
    "code": "pascal"
  },
  {
    "code": "snake"
  },
  {
    "code": "macro"
  },
  {
    "code": "cobol"
  },
  {
    "code": "flat"
  }
]

/req/functions/lengthlinterin section length

length MUST fail when the length of the value is below the option min or above the option max, where the length of a string is its number of UTF-16 code units, of an array its number of elements, of an object its number of properties, and of a number the number itself.

/conf/functions/lengthlength with max: 4 fails on a string of six characters and an array of five elements, and passes on a number below the bound.2 results

Covers /req/functions/length. Fixtures in conformance/functions/length/.

ruleset.yaml

rules:
  bounded:
    given: $.*
    then:
      function: length
      functionOptions:
        max: 4

document.yaml

short: ab
long: abcdef
many: [1, 2, 3, 4, 5]
number: 3

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "path": [
      "long"
    ]
  },
  {
    "path": [
      "many"
    ]
  }
]
/conf/functions/length-code-unitsA string’s length is its number of UTF-16 code units: é is one, an emoji two.1 result

Covers /req/functions/length. Fixtures in conformance/functions/length-code-units/.

ruleset.yaml

rules:
  r:
    given: [$.e, $.emoji]
    then:
      function: length
      functionOptions:
        min: 2

document.yaml

e: "é"
emoji: "😀"

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "e"
    ]
  }
]
/conf/functions/length-minlength with min: 2 fails on an object with one property and a string of one character, and passes on an object with two.2 results

Covers /req/functions/length. Fixtures in conformance/functions/length-min/.

ruleset.yaml

rules:
  at-least-two:
    given: $.*
    then:
      function: length
      functionOptions:
        min: 2

document.yaml

o:
  a: 1
p:
  a: 1
  b: 2
s: x

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "at-least-two",
    "path": [
      "o"
    ]
  },
  {
    "code": "at-least-two",
    "path": [
      "s"
    ]
  }
]

/req/functions/alphabeticallinterin section alphabetical

alphabetical MUST fail when the elements of an array, or the keys of an object, are not in ascending order, where numbers are compared by value, strings as localeCompare of ECMAScript ECMA-402 compares them with no locale given, and two values of different types count as in order. With the option keyedBy, the elements of an array of objects are compared by that property.

/conf/functions/alphabeticalalphabetical fails on an unsorted array and passes on a sorted one.1 result

Covers /req/functions/alphabetical. Fixtures in conformance/functions/alphabetical/.

ruleset.yaml

rules:
  sorted:
    given: $.*
    then:
      function: alphabetical

document.yaml

tags: [b, a]
ok: [a, b]

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "sorted"
  }
]
/conf/functions/alphabetical-caseStrings are compared with localeCompare, so a comes before B and B after a.1 result

Covers /req/functions/alphabetical. Fixtures in conformance/functions/alphabetical-case/.

ruleset.yaml

rules:
  r:
    given: [$.ok, $.bad]
    then:
      function: alphabetical

document.yaml

ok: [a, B]
bad: [B, a]

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "bad",
      "0"
    ]
  }
]
/conf/functions/alphabetical-keyed-byWith keyedBy, an array of objects is compared by that property.1 result

Covers /req/functions/alphabetical. Fixtures in conformance/functions/alphabetical-keyed-by/.

ruleset.yaml

rules:
  tags-sorted:
    given: $.*
    then:
      function: alphabetical
      functionOptions:
        keyedBy: name

document.yaml

unsorted:
  - name: b
  - name: a
sorted:
  - name: a
  - name: b

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "tags-sorted"
  }
]
/conf/functions/alphabetical-numbersNumbers are compared by value, so [2, 10] is in order and [10, 9] is not.1 result

Covers /req/functions/alphabetical. Fixtures in conformance/functions/alphabetical-numbers/.

ruleset.yaml

rules:
  r:
    given: [$.ok, $.bad]
    then:
      function: alphabetical

document.yaml

ok: [2, 10]
bad: [10, 9]

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "bad",
      "0"
    ]
  }
]

/req/functions/enumerationlinterin section enumeration

enumeration MUST fail when the value is not one of the elements of the option values, compared by JSON type and value, so that "1" is not 1 while 1.0 is.

/conf/functions/enumerationenumeration fails on a value outside values.1 result

Covers /req/functions/enumeration. Fixtures in conformance/functions/enumeration/.

ruleset.yaml

rules:
  allowed:
    given: $.*
    then:
      function: enumeration
      functionOptions:
        values: [a, b]

document.yaml

x: c
y: a

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "path": [
      "x"
    ]
  }
]
/conf/functions/enumeration-types"1" is not one of [1], while 1.0 is.1 result

Covers /req/functions/enumeration. Fixtures in conformance/functions/enumeration-types/.

ruleset.yaml

rules:
  r:
    given: [$.s, $.n]
    then:
      function: enumeration
      functionOptions:
        values: [1]

document.yaml

s: "1"
n: 1.0

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "s"
    ]
  }
]

/req/functions/schemalinterin section schema

schema MUST fail when the value is absent, or when it does not validate against the JSON Schema given as the option schema, asserting the format keyword and resolving a $ref within the option against the option itself, with one problem for the first failed keyword, or one per failed keyword when the option allErrors is true. The schema is read in the draft the option dialect names, as the table above defines each draft; with dialect absent or auto, in the draft its $schema names, and in draft 7 when it names none.

/conf/functions/schemaschema fails on a value that does not validate against the given JSON Schema.1 result

Covers /req/functions/schema. Fixtures in conformance/functions/schema/.

ruleset.yaml

rules:
  strings:
    given: $.*
    then:
      function: schema
      functionOptions:
        schema:
          type: string

document.yaml

x: 1
y: text

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "path": [
      "x"
    ]
  }
]
/conf/functions/schema-absentschema fails on an absent value.1 result

Covers /req/functions/schema. Fixtures in conformance/functions/schema-absent/.

ruleset.yaml

rules:
  contact-shape:
    given: $.info
    then:
      field: contact
      function: schema
      functionOptions:
        schema:
          type: object

document.yaml

info:
  title: Petstore

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "contact-shape"
  }
]
/conf/functions/schema-all-errorsWith allErrors: true, a value that fails two keywords gets two problems.2 results

Covers /req/functions/schema. Fixtures in conformance/functions/schema-all-errors/.

ruleset.yaml

rules:
  r:
    given: $.v
    then:
      function: schema
      functionOptions:
        schema: {"type": "string", "minLength": 3, "pattern": "^y"}
        allErrors: true

document.yaml

v: x

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "v"
    ]
  },
  {
    "code": "r",
    "path": [
      "v"
    ]
  }
]
/conf/functions/schema-default-draftWithout $schema or dialect, the schema is read as draft 7: prefixItems is ignored, required is not.1 result

Covers /req/functions/schema. Fixtures in conformance/functions/schema-default-draft/.

ruleset.yaml

rules:
  prefix:
    given: $.t
    then:
      function: schema
      functionOptions:
        schema:
          type: array
          prefixItems:
            - type: string
  needs-a:
    given: $.o
    then:
      function: schema
      functionOptions:
        schema:
          required: [a]

document.yaml

t: [1]
o: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "needs-a"
  }
]
/conf/functions/schema-dialectWith dialect: draft2020-12, prefixItems applies.1 result

Covers /req/functions/schema. Fixtures in conformance/functions/schema-dialect/.

ruleset.yaml

rules:
  prefix:
    given: $.t
    then:
      function: schema
      functionOptions:
        dialect: draft2020-12
        schema:
          type: array
          prefixItems:
            - type: string

document.yaml

t: [1]

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "prefix"
  }
]
/conf/functions/schema-first-errorWithout allErrors, a value that fails two keywords gets one problem.1 result

Covers /req/functions/schema. Fixtures in conformance/functions/schema-first-error/.

ruleset.yaml

rules:
  r:
    given: $.v
    then:
      function: schema
      functionOptions:
        schema: {"type": "string", "minLength": 3, "pattern": "^y"}

document.yaml

v: x

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "v"
    ]
  }
]
/conf/functions/schema-formatThe format keyword is asserted, so not-an-email fails format: email.1 result

Covers /req/functions/schema. Fixtures in conformance/functions/schema-format/.

ruleset.yaml

rules:
  r:
    given: [$.bad, $.ok]
    then:
      function: schema
      functionOptions:
        schema: {"type": "string", "format": "email"}

document.yaml

bad: not-an-email
ok: a@b.co

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "bad"
    ]
  }
]
/conf/functions/schema-refA $ref inside the option resolves against the option’s own $defs.1 result

Covers /req/functions/schema. Fixtures in conformance/functions/schema-ref/.

ruleset.yaml

rules:
  r:
    given: $.v
    then:
      function: schema
      functionOptions:
        schema: {"$ref": "#/$defs/x", "$defs": {"x": {"type": "integer"}}}

document.yaml

v: text

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "v"
    ]
  }
]

/req/functions/xorlinterin section xor

xor MUST fail unless exactly one of the properties named by the option properties is present on the value, where a property is present when the object has the key, whatever its value.

/conf/functions/xorxor fails unless exactly one of properties is present.2 results

Covers /req/functions/xor. Fixtures in conformance/functions/xor/.

ruleset.yaml

rules:
  one-of:
    given: $.*
    then:
      function: xor
      functionOptions:
        properties: [p, q]

document.yaml

both:
  p: 1
  q: 1
one:
  p: 1
neither: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "path": [
      "both"
    ]
  },
  {
    "path": [
      "neither"
    ]
  }
]
/conf/functions/xor-nullA key whose value is null counts as present, so {a: null, b: 1} has two of the properties.1 result

Covers /req/functions/xor. Fixtures in conformance/functions/xor-null/.

ruleset.yaml

rules:
  r:
    given: [$.one, $.two]
    then:
      function: xor
      functionOptions:
        properties: ["a", "b"]

document.yaml

one: {a: null}
two: {a: null, b: 1}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "two"
    ]
  }
]

/req/functions/orlinterin section or

or MUST fail unless at least one of the properties named by the option properties is present on the value, where a property is present when the object has the key, whatever its value.

/conf/functions/oror fails unless at least one of properties is present.1 result

Covers /req/functions/or. Fixtures in conformance/functions/or/.

ruleset.yaml

rules:
  any-of:
    given: $.*
    then:
      function: or
      functionOptions:
        properties: [p, q]

document.yaml

both:
  p: 1
  q: 1
one:
  p: 1
neither: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "path": [
      "neither"
    ]
  }
]
/conf/functions/or-nullA key whose value is null counts as present, so {a: null} satisfies or and {} does not.1 result

Covers /req/functions/or. Fixtures in conformance/functions/or-null/.

ruleset.yaml

rules:
  r:
    given: [$.one, $.none]
    then:
      function: or
      functionOptions:
        properties: ["a", "b"]

document.yaml

one: {a: null}
none: {}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "path": [
      "none"
    ]
  }
]

/req/functions/unreferenced-reusable-objectlinterin section unreferencedReusableObject

unreferencedReusableObject, applied to an object of reusable definitions, MUST fail once for each member that no $ref in the documents being linted points to, where the option reusableObjectsLocation is the JSON Pointer RFC6901 of that object in the document.

/conf/functions/unreferenced-reusable-objectunreferencedReusableObject reports a member of the container that nothing references.1 result

Covers /req/functions/unreferenced-reusable-object. Fixtures in conformance/functions/unreferenced-reusable-object/.

ruleset.yaml

rules:
  no-unused-schemas:
    resolved: false
    given: $.components.schemas
    then:
      function: unreferencedReusableObject
      functionOptions:
        reusableObjectsLocation: "#/components/schemas"

document.yaml

paths:
  /pets:
    get:
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Used"
components:
  schemas:
    Used:
      type: object
    Unused:
      type: object

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "path": [
      "components",
      "schemas",
      "Unused"
    ]
  }
]

Conformance class /conf/linter-custom-functions

/req/custom-functions/missinglinterin section The contract

A linter MUST reject a ruleset that names a function it cannot load.

/conf/custom-functions/presentA ruleset naming a function whose file exists is loaded and the function runs.1 result

Covers /req/custom-functions/missing. Fixtures in conformance/custom-functions/present/.

ruleset.yaml

functions:
  - isEmptyTitle
rules:
  title-filled:
    given: $.info.title
    then:
      function: isEmptyTitle

document.yaml

info:
  title: ""

functions/isEmptyTitle.js

module.exports = (v) => (typeof v === 'string' && v.trim() !== '' ? [] : [{ message: 'Title is empty.' }]);

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "title-filled"
  }
]
/conf/custom-functions/missingA ruleset naming a function whose file does not exist is rejected.rejected

Covers /req/custom-functions/missing. Fixtures in conformance/custom-functions/missing/.

ruleset.yaml

functions:
  - doesNotExist
rules:
  r:
    given: $
    then:
      function: doesNotExist

document.yaml

{}

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

The linter rejects the ruleset and produces no results.

/req/custom-functions/invokelinterin section The contract

A linter MUST call a custom function with the value the invocation targets and the invocation’s functionOptions, and MUST report each problem the function returns as a result whose message is the problem’s message.

/conf/custom-functions/invokeThe function receives the value and the options, and each problem it returns becomes a result with its message.2 results

Covers /req/custom-functions/invoke. Fixtures in conformance/custom-functions/invoke/.

ruleset.yaml

functions:
  - forbiddenWords
rules:
  no-forbidden-words:
    given: $.info.title
    then:
      function: forbiddenWords
      functionOptions:
        forbidden: [evil, bad]

document.yaml

info:
  title: An evil and bad title

functions/forbiddenWords.js

module.exports = (targetVal, options) => {
  const problems = [];
  for (const word of options.forbidden) {
    if (String(targetVal).includes(word)) {
      problems.push({ message: Contains &quot;${word}&quot;. });
    }
  }
  return problems;
};

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "no-forbidden-words",
    "message": "Contains \"evil\"."
  },
  {
    "code": "no-forbidden-words",
    "message": "Contains \"bad\"."
  }
]

/req/custom-functions/problem-pathlinterin section The contract

A linter MUST report a problem that carries a path at that path in the document, a problem without one at the matched node, and a problem whose path is not in the document at the document’s root.

/conf/custom-functions/problem-pathA problem with a path is reported there; one whose path is not in the document is reported at the root.2 results

Covers /req/custom-functions/problem-path. Fixtures in conformance/custom-functions/problem-path/.

ruleset.yaml

functions: [pathed, elsewhere]
rules:
  pathed:
    given: $.info
    then:
      function: pathed
  elsewhere:
    given: $.info
    then:
      function: elsewhere

document.yaml

info:
  title: x

functions/pathed.js

module.exports = (value, options) => [{ message: 'at the title', path: ['info', 'title'] }];

functions/elsewhere.js

module.exports = (value, options) => [{ message: 'nowhere', path: ['nope', 'deeper'] }];

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 2 results, matching on the fields given, in any order:

[
  {
    "code": "pathed",
    "path": [
      "info",
      "title"
    ]
  },
  {
    "code": "elsewhere",
    "path": []
  }
]

/req/custom-functions/no-problemslinterin section The contract

A linter MUST treat a custom function that returns nothing as returning no problems.

/conf/custom-functions/returns-nothingA function that returns nothing reports no problems, while one that returns a problem does.1 result

Covers /req/custom-functions/no-problems. Fixtures in conformance/custom-functions/returns-nothing/.

ruleset.yaml

functions: [nothing, one]
rules:
  quiet:
    given: $.info
    then:
      function: nothing
  loud:
    given: $.info
    then:
      function: one

document.yaml

info:
  title: x

functions/nothing.js

module.exports = (value, options) => { return; };

functions/one.js

module.exports = (value, options) => [{ message: 'one problem' }];

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "loud",
    "path": [
      "info"
    ]
  }
]

Conformance class /conf/linter-custom-functions-javascript

/req/custom-functions/javascript-loadlinterin section JavaScript binding

A linter MUST load each name in functions from /.js, resolved relative to the ruleset file, with functions as the directory when functionsDir is absent.

/conf/custom-functions/load-custom-dirWith functionsDir, the function is loaded from that directory.1 result

Covers /req/custom-functions/javascript-load. Fixtures in conformance/custom-functions/load-custom-dir/.

ruleset.yaml

functions:
  - isEmptyTitle
functionsDir: ./checks
rules:
  title-filled:
    given: $.info.title
    then:
      function: isEmptyTitle

document.yaml

info:
  title: "  "

checks/isEmptyTitle.js

module.exports = (targetVal) => {
  if (typeof targetVal !== 'string' || targetVal.trim() === '') {
    return [{ message: 'Title is empty.' }];
  }
  return [];
};

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "title-filled",
    "message": "Title is empty."
  }
]
/conf/custom-functions/load-default-dirA function named in functions is loaded from functions/<name>.js next to the ruleset.1 result

Covers /req/custom-functions/javascript-load. Fixtures in conformance/custom-functions/load-default-dir/.

ruleset.yaml

functions:
  - isEmptyTitle
rules:
  title-filled:
    given: $.info.title
    then:
      function: isEmptyTitle

document.yaml

info:
  title: "  "

functions/isEmptyTitle.js

module.exports = (targetVal) => {
  if (typeof targetVal !== 'string' || targetVal.trim() === '') {
    return [{ message: 'Title is empty.' }];
  }
  return [];
};

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "title-filled",
    "message": "Title is empty."
  }
]

/req/custom-functions/javascript-promiselinterin section JavaScript binding

When a JavaScript custom function returns a promise, a linter MUST wait for it and use the problems it resolves to.

/conf/custom-functions/javascript-promiseA function that returns a promise is awaited, and the problems it resolves to are reported.1 result

Covers /req/custom-functions/javascript-promise. Fixtures in conformance/custom-functions/javascript-promise/.

ruleset.yaml

functions: [later]
rules:
  r:
    given: $.info
    then:
      function: later

document.yaml

info:
  title: x

functions/later.js

module.exports = async (value, options) => [{ message: 'from a promise' }];

Procedure

Lint document.yaml with ruleset.yaml and collect the results as JSON.

Expected

Linting produces exactly 1 result, matching on the fields given, in any order:

[
  {
    "code": "r",
    "message": "from a promise"
  }
]

Appendix C: Changes

There is no release yet. From the first release on, this appendix lists what changed since the latest one.

Appendix D: Using the schema

This appendix shows how to use the ruleset schema while writing rulesets. The examples use https://spec.openlint.org/v1/ruleset.schema.json, which follows the newest 1.x release; see § 1.2 Versions of this document. The format reserves no $schema key and rejects unknown root properties, so bind the schema from outside the document.

For a YAML ruleset, add a modeline comment:

# yaml-language-server: $schema=https://spec.openlint.org/v1/ruleset.schema.json
extends: spectral:oas

For a JSON ruleset in VS Code, map the schema to your ruleset’s file name in .vscode/settings.json:

{
  "json.schemas": [
    {
      "fileMatch": ["ruleset.json"],
      "url": "https://spec.openlint.org/v1/ruleset.schema.json"
    }
  ]
}

In CI, any JSON Schema 2020-12 validator works:

npx ajv-cli validate --spec=draft2020 --strict=false \
  -s ruleset.schema.json \
  -d ruleset.yaml

Validating a ruleset against the schema catches misspelled keywords and invalid overrides before any document is linted. It does not catch everything. A JSON Schema validates the shape of one parsed value, so it cannot check that the file parses, that a name refers to something declared elsewhere, or that function options match the named function. The schema accepts rulesets that violate these requirements, and a linter rejects them:

A linter also rejects, for the documents an override applies to, what /req/overrides/checked-when-applied lists. The schema checks that a given value begins with $ or #, but does not parse it as JSONPath. A syntactically invalid JSONPath expression passes the schema and fails at lint time.

Appendix E: Provenance

The format described here originates in Spectral [SPECTRAL] by Stoplight, licensed Apache-2.0. OpenLint, the reference implementation, is openlint/openlint, forked from Spectral; the issues in the working draft describe its behavior by name. The specification and schema were first derived from Spectral’s validation meta-schemas and guides. The OpenLint community drafted them in api-commons before the project had its name, and then moved them to openlint/spec. Its conformance classes follow the model of the OGC API standards.

Index

Terms defined by this specification

References

Normative References

[ECMA-262-2025]
ECMAScript 2025 Language Specification, 16th edition (ECMA-262). June 2025. URL: https://262.ecma-international.org/16.0/
[ECMA-402]
ECMAScript 2025 Internationalization API Specification, 12th edition (ECMA-402). URL: https://262.ecma-international.org/
[JSON-SCHEMA-CORE-2020-12]
Austin Wright; et al. JSON Schema: A Media Type for Describing JSON Documents, draft-bhutton-json-schema-01 (2020-12). 16 June 2022. URL: https://json-schema.org/draft/2020-12/json-schema-core
[JSON-SCHEMA-DRAFT-04]
Francis Galiegue; Kris Zyp; Gary Court. JSON Schema: core definitions and terminology, draft-zyp-json-schema-04. URL: https://json-schema.org/draft-04/draft-zyp-json-schema-04
[JSON-SCHEMA-DRAFT-06]
Austin Wright. JSON Schema: A Media Type for Describing JSON Documents, draft-wright-json-schema-01 (draft 6). URL: https://json-schema.org/draft-06/draft-wright-json-schema-01
[JSON-SCHEMA-DRAFT-07]
Austin Wright; Henry Andrews. JSON Schema: A Media Type for Describing JSON Documents, draft-handrews-json-schema-01 (draft 7). 19 March 2018. URL: https://json-schema.org/draft-07/draft-handrews-json-schema-01
[JSON-SCHEMA-DRAFT-2019-09]
Austin Wright; et al. JSON Schema: A Media Type for Describing JSON Documents, draft-handrews-json-schema-02 (2019-09). URL: https://json-schema.org/draft/2019-09/draft-handrews-json-schema-02
[JSON-SCHEMA-VALIDATION-2020-12]
Austin Wright; Henry Andrews; Ben Hutton. JSON Schema Validation: A Vocabulary for Structural Validation of JSON, draft-bhutton-json-schema-validation-01 (2020-12). 16 June 2022. URL: https://json-schema.org/draft/2020-12/json-schema-validation
[JSONPATH-PLUS]
JSONPath-Plus 10.4.0. URL: https://github.com/JSONPath-Plus/JSONPath/tree/v10.4.0
[MINIMATCH]
minimatch 3.1.5. URL: https://github.com/isaacs/minimatch/tree/v3.1.5
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC3986]
T. Berners-Lee; R. Fielding; L. Masinter. Uniform Resource Identifier (URI): Generic Syntax. January 2005. Internet Standard. URL: https://www.rfc-editor.org/info/rfc3986/
[RFC6901]
P. Bryan, Ed.; K. Zyp; M. Nottingham, Ed.. JavaScript Object Notation (JSON) Pointer. April 2013. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc6901/
[RFC8174]
B. Leiba. Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc8174/
[RFC8259]
T. Bray, Ed.. The JavaScript Object Notation (JSON) Data Interchange Format. December 2017. Internet Standard. URL: https://www.rfc-editor.org/info/rfc8259/
[YAML-1.2.2]
Oren Ben-Kiki; et al. YAML Ain't Markup Language (YAML) Version 1.2, Revision 1.2.2. 1 October 2021. URL: https://yaml.org/spec/1.2.2/

Non-Normative References

[RFC9485]
C. Bormann; T. Bray. I-Regexp: An Interoperable Regular Expression Format. October 2023. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9485/
[RFC9535]
S. Gössner, Ed.; G. Normington, Ed.; C. Bormann, Ed.. JSONPath: Query Expressions for JSON. February 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9535/
[SEMVER]
Tom Preston-Werner. Semantic Versioning 2.0.0. URL: https://semver.org/spec/v2.0.0.html
[SPECTRAL]
Spectral: a flexible JSON/YAML linter. URL: https://github.com/stoplightio/spectral

Issues Index

What counts as a change that breaks conformance, and what is promised to whom, is discussed in discussion #12. ↵
Whether path-style identifiers work for everyone is an open question in discussion #13. Alternatives such as numeric identifiers (REQ-042) or dotted names (given.no-match) have not been raised there yet. ↵
Whether these tests become a published conformance suite, where its runner would live, and how a linter would publish its results against it, is not decided. ↵
/req/ruleset/single-document is proposed, because it changes current behavior. Spectral and OpenLint accept a YAML file with several documents separated by ---, read the first, and drop the others without reporting them; neither documents this. A ruleset split by a stray --- therefore loses rules silently, and rejecting the file catches that. Until both linters change, /conf/ruleset/document-two-yaml-documents fails against them. Whether this counts as a breaking change for 1.x is part of discussion #12. ↵
documentationUrl takes only an absolute http, https, or ftp URL with a public host, which is how the url format of OpenLint’s validator reads a URL. The schema spells that out as a pattern. It rejects mailto: and urn: URIs, localhost, single-label intranet hosts such as http://wiki/docs, private and loopback addresses, and punycode host names; see /conf/ruleset/documentation-url-intranet. Accepting any absolute URI [RFC3986] would let a ruleset link to internal documentation. ↵
Elements after the mode are accepted and ignored, so [./base.yaml, all, extra] reads as [./base.yaml, all]. OpenLint accepts them although its own validation schema forbids them for this array. Rejecting them would catch a misplaced bracket. ↵
A numeric shorthand such as r: 0 is rejected only by OpenLint’s validation schema. Its type definitions and its merge code accept a number and would apply it as the severity. Accepting numbers in shorthands would remove the difference with severity without making any ruleset invalid. ↵
A glob dialect with a specification of its own, or a written subset of minimatch’s, would let a linter in another language match files without reproducing minimatch. ↵
OpenLint runs custom functions without isolation. Whether a conforming linter has to sandbox them, or refuse functions that come from a remote ruleset, is open. ↵
A redefined rule keeps resolving its alias references against the ruleset that first defined it, so redefining an inherited rule with an alias reference works only when both rulesets declare the alias, and then uses the inherited ruleset’s. Resolving against the redefining ruleset’s aliases would let an extending ruleset retarget a rule with aliases of its own. ↵
then: [] is accepted, and the rule then asserts nothing and never reports a result. Rejecting an empty array would catch a rule left unfinished. ↵
An unknown property inside an invocation, such as functionOption without the s, is ignored. When the function has no required option it runs with its defaults; when it has one, the ruleset is rejected for the missing functionOptions with a message that does not point at the misspelling. Rejecting the unknown property would catch the misspelling in both cases. ↵
A UTF-8 byte order mark is accepted, but OpenLint then reports range columns on every line after the first one too far to the left. A UTF-16 document is not rejected; it parses as garbage and produces parser results. Ignoring a byte order mark and rejecting other encodings would make both deterministic. ↵
Whether rules run against the part of a document before a syntax error is not stated. OpenLint runs them against whatever tree its parser recovers, which for an unterminated flow collection can be nothing. ↵
Non-string scalar keys are the one construct OpenLint reports under incompatibleValues. A mapping key that is itself a sequence or a mapping is reported as a warning by the YAML parser, with no option controlling it, and a !!binary or !!set tag ends the run with an error before any result. Whether those constructs belong under incompatibleValues, and whether a tag the linter cannot represent is a parser result, is open. ↵
What a rule sees at the position of a reference in a cycle, and whether resolution follows $ref in every position or only where the document’s format expects one, is not stated. ↵
The set of identifiers is closed, because OpenLint rejects an identifier it does not register. The previous document and schema accepted any string, so that formats added later, or registered by a host application, would stay valid. Which of the two this specification wants is open. ↵
How a linter resolves file paths, npm module specifiers, and URLs, and what each is relative to, is implementation-defined and has to be specified. So is whether every conforming linter has to recognize the built-in identifiers, which name rulesets shipped with OpenLint; discussion #6 uses openlint:openapi as the name of a new core OpenAPI ruleset. Resolving an npm module specifier also ties linter-core to the JavaScript package ecosystem, so it could move to a class of its own, as custom functions have. ↵
In OpenLint, a cycle through extends between ruleset files is neither detected nor reported: the loader follows it until the process runs out of memory, so the ruleset never loads. Its core skips a definition it is already loading, but only for definitions held in memory. A conforming linter has to reject cycles, or the ruleset has to be invalid; which is open. ↵
An override with extends and no rules drops, for the documents it matches, every rule the ruleset inherits through its own extends, while its own rules stay. With rules, it adds to them. Adding in both cases would make the two forms consistent. ↵
An alias declared in an override does not reach the rules the ruleset defines, so overriding an alias by name changes nothing for them. The previous document said an override’s aliases merge by name with the ruleset’s. Applying them to every rule, for the documents the override matches, would make that true. ↵
These three checks run only when the override applies to a document, as they do in OpenLint. Whether a Rule Object in an override’s rules merges with the rule it redefines, as /req/rules/redefinition-merge says for extends, or replaces it, is not tested. A ruleset with such an error loads without error until the first document the override matches. Everything else in an override is checked when the ruleset loads. Checking these three at load time too would report the error before any document matches. ↵
A redefinition keeps every property it does not set, so the base’s message or severity survives a redefinition that changes given and then, and can stop matching what the rule now checks. Replacing the whole rule would make a redefinition independent of the rule it replaces. ↵
Because detection is limited to the identifiers a ruleset names, a scoped alias whose target names a format no rule names never uses that target, even for a document of that format. Detecting every identifier in the table would make scoped aliases independent of the rules that use them. ↵
The conditions in the table are the ones OpenLint applies. They read the string form of a version value, so openapi: 3.0 written as a YAML number is OpenAPI 3 but not 3.0, and they accept openapi: 3abc. Conditions on the typed value would be stricter. Which document types the format supports is discussed in discussion #5. ↵
JSONPath-Plus has no specification of its own, and its filter expressions are JavaScript. OpenLint evaluates most expressions with Nimma and falls back to JSONPath-Plus for the rest; a filter in the syntax of JSONPath [RFC9535], ?@.in == 'header', matches nothing in it. Making RFC 9535 normative would give the dialect a specification, at the cost of the rulesets that use JavaScript filters. ↵
Only the last matching target counts, so a document with two formats, such as oas3 and oas3_1, uses one target even when two match. Using every matching target, or the first, would read more naturally as a list. ↵
Indices in path are strings, ["names", "1"], as OpenLint reports them. Numbers would tell an index apart from a key that happens to be numeric. ↵
{{value}} substitutes a string as it is and an object as [object Object], which no ruleset wants. A JSON serialization of the value would be useful, and is a change in output. ↵
pattern takes the regular expressions and flags of ECMAScript, so a linter written in another language has to reproduce ECMAScript’s semantics. I-Regexp [RFC9485] is a dialect meant to behave the same in every language, but it has no lookaround or backreferences, so rulesets that use them would break. Which dialect a later version adopts is open. ↵
The exact character classes of each case have to be stated before this requirement is complete. ↵
localeCompare without a locale uses the locale of the environment, so the order of strings such as ä and z can differ between machines. A fixed collation, such as code point order, would make the function portable at the cost of changing the order of mixed-case names. ↵
A linter written in Go needs a Go binding, and this version defines none. vacuum, a linter written in Go, loads custom functions as Go plugins from a directory named on its command line, registered under the name each function reports, and does not read functions or functionsDir. A Go binding would have to say where a linter finds a function, which symbol it loads, and the function’s signature in types this specification owns. Go plugins also load only into a binary built with the same Go version and package versions, so a plugin built for one linter does not load into another. ↵
A binding ties a function to one language, so a ruleset whose functions are written in that language runs only on linters that bind it. A binding to WebAssembly, or to a function that runs as a separate process, would let any linter run functions written in any language that targets it. Whether a later version defines such a binding is open. ↵
A function that throws, or returns something that is not a list of problems, ends the run with no results and an error. Reporting it as a result for the rule, with the error as its message, would keep the results of the other rules. ↵
A function module in CommonJS and one in ES module syntax both load in OpenLint, and the function receives a third argument with the document, the rule, and the path. Neither is stated normatively. ↵