Migrate from Spectral

This guide moves a repository that lints its API descriptions with Spectral to apigenie lint: the built-in rules, your own rules and functions, the report in CI, and what has no counterpart. It starts from Spectral 6.17 with its spectral:oas and spectral:asyncapi rulesets and an OpenAPI file, openapi.yaml.

You need the CLI installed; the Quickstart shows how. Keep Spectral installed until the last step, so you can compare the two reports on the same files.

What changes and what stays

Spectral API Genie
Command spectral lint openapi.yaml apigenie lint openapi.yaml
Configuration .spectral.yaml, .spectral.json or .spectral.js apigenie.yaml, section commands.lint (Configuration)
Built-in rules spectral:oas (57 rules), spectral:asyncapi (55) rulesets per standard: recommended, strict, all; 64 rules for OpenAPI, 48 for Swagger 2.0, 34 for AsyncAPI
Severities error, warn, info, hint, off error, warning, hint (or info), off; a custom rule writes warn
Custom rules given, then, functions the same format, under commands.lint.rules
Custom functions JavaScript, functionsDir JavaScript in functions/, listed under functions
Disabling a rule in one place overrides by file in the ruleset x-apigenie-lint-disable in the document
Report formats -f json, junit, sarif, html, … --formatter json, junit, sarif, html, …
Exit status 1 at --fail-severity and above, error by default 1 on an error, 0 on warnings only, 2 on a file it cannot read
Fixes none --fix for the issues that have one

Run both on the same file

spectral lint openapi.yaml
apigenie lint openapi.yaml
  764:6   warning  Unused shared component                                 unused-shared-component
  781:8   error    Schema with integer type does not define proper format  schema-format-defined

  2 issues (1 error, 1 warning)
  0 errors, 1 warning are potentially auto-fixable

Each line gives the position, the severity, the message and the rule; every rule has a page in the linter rules that says what it checks and why. The two reports differ, and not only in the names: without configuration API Genie applies the recommended ruleset, 43 rules of which some have no Spectral counterpart, and leaves out some of what spectral:oas reports. The next two sections say which.

Replace the ruleset

A .spectral.yaml that extends the built-in ruleset and adjusts a few rules:

extends: spectral:oas
rules:
  operation-description: error
  info-contact: off
  oas3-server-not-example.com: warn

becomes, in apigenie.yaml at the root of the repository:

version: '1.0'
commands:
  lint:
    extends: [recommended]
    rules:
      operation-description-defined: error
      contact-defined: off
  • extends names API Genie’s rulesets, not Spectral’s. recommended is the default; strict turns on more rules with higher severities, all every rule. The rulesets of each standard are listed in the linter rules.
  • A rule takes its name from the table below. Spectral’s warn is warning for a built-in rule; hint and off stay.
  • A rule with no counterpart, such as oas3-server-not-example.com, has no line.
  • An OpenAPI 3.x file, a Swagger 2.0 file and an AsyncAPI file are linted by three linters with rules of their own, so one configuration covers a repository that mixes them; Spectral picks the ruleset by the document’s format.

The rules of spectral:oas, by name

The rules Spectral turns on by default, and their counterparts in the OpenAPI linter. A rule marked not checked has no counterpart today: drop its line from the configuration.

Spectral API Genie Note
operation-success-response operation-success-response
operation-operationId operation-id-defined
operation-operationId-unique operation-id-unique
operation-operationId-valid-in-url not checked
operation-parameters parameters-unique
operation-tags operation-tags-defined
operation-tag-defined not checked a tag an operation uses without the document declaring it
operation-description operation-description-defined also rejects descriptions under 10 characters
path-params path-parameters-defined path-parameters-on-items asks for them on the path item
path-declarations-must-exist, path-keys-no-trailing-slash paths-well-formed
path-not-include-query not checked
duplicated-entry-in-enum schema-enum-no-duplicates
typed-enum schema-enum-types
array-items, no-$ref-siblings not checked
info-contact contact-defined asks for name, and url or email, as Spectral’s contact-properties does
info-description info-defined asks for summary, description and version
no-eval-in-markdown, no-script-tags-in-markdown safe-markdown
openapi-tags-uniqueness tags-unique
oas3-api-servers servers-defined
oas3-server-trailing-slash, oas3-server-variables not checked server-urls checks for http and upper-case letters instead
oas3-operation-security-defined operation-security-defined a different check: every operation has a security requirement; Spectral checks that the named schemes exist
oas3-schema openapi-json-schema-validation
oas3-unused-component unused-shared-component follows references transitively, so a component used only by an unused one is reported too
oas3-examples-value-or-externalValue, oas3-valid-media-example, oas3-valid-schema-example not checked examples are not validated against their schema
oas3-callbacks-in-callbacks, oas3_1-servers-in-webhook, oas3_1-callbacks-in-webhook, oas3_2-no-deprecated-xml-attribute not checked

The rules Spectral leaves off by default:

Spectral API Genie
contact-properties contact-defined
info-license license-defined
license-url not checked
openapi-tags not checked
openapi-tags-alphabetical tags-sorted
operation-singular-tag not checked
tag-description tag-description-defined
oas3-parameter-description parameter-description-defined
oas3-server-not-example.com not checked

The oas2-* rules apply to Swagger 2.0 files, which API Genie lints with the Swagger 2.0 linter: oas2-schema is swagger-validation, oas2-api-host is host-defined, oas2-unused-definition is unused-shared-component, and the rules that share a name with an OpenAPI rule check the same thing. The AsyncAPI rules of spectral:asyncapi map onto the AsyncAPI linter the same way: asyncapi-info-contact is contact-defined, asyncapi-channel-parameters is channel-parameters-defined, asyncapi-unused-components-schema is unused-shared-component.

Rules of recommended that spectral:oas has nothing for; each has a page that says what it checks and why:

Outside recommended: spell-check for the texts written for humans, schema-keyword-support for the JSON Schema keywords the tools around the document ignore, and the privacy ruleset for credentials and personal data in URLs. extends: [recommended, privacy] adds the last one.

Carry your custom rules over

A custom rule keeps its definition. This rule of .spectral.yaml:

rules:
  operation-summary:
    description: Operations must have a summary
    message: "{{property}} is missing at {{path}}"
    severity: error
    given: "$.paths[*][get,put,post,delete,patch]"
    then:
      field: summary
      function: truthy

goes under commands.lint.rules of apigenie.yaml as it is:

version: '1.0'
commands:
  lint:
    extends: [recommended]
    rules:
      operation-summary:
        description: Operations must have a summary
        message: "{{property}} is missing at {{path}}"
        severity: error
        given: "$.paths[*][get,put,post,delete,patch]"
        then:
          field: summary
          function: truthy

What carries over, as custom rules documents it:

  • given, then with field, function and functionOptions, message with {{error}}, {{description}}, {{path}}, {{property}} and {{value}}, description, severity, resolved and formats (oas2, oas3, oas3_0, oas3_1, oas3_2, aas3, …).
  • The core functions: truthy, falsy, defined, undefined, enumeration, length, pattern, casing, alphabetical, or, xor, schema, typedEnum and unreferencedReusableObject, with their options.
  • JSONPath as Spectral rulesets write it: member unions without quotes, ~ for member names, and filters written as JavaScript expressions with @property, @parent, @path and the string methods (JSONPath).

What does not:

  • aliases and the #Name references to them: write the path out.
  • Script expressions such as [(@.length-1)].
  • extends with a file or a URL: the rules of a shared ruleset go into the configuration, or into one file that every repository passes with --config.
  • overrides, parserOptions, documentationUrl and except.
  • A custom rule named like a built-in rule: rename it.

Carry your functions over

A JavaScript function written for Spectral works unchanged in most cases: it is an ES module whose default export receives the value, the options and a context, and @stoplight/spectral-core and @stoplight/spectral-functions are built in. Put the files in functions/ next to apigenie.yaml (functionsDir changes the directory) and list them:

commands:
  lint:
    functions: [operationIdPrefix]
    rules:
      operation-id-prefix:
        given: "$.paths[*][*].operationId"
        then:
          function: operationIdPrefix
          functionOptions: { prefix: api }

Functions run in a sandbox with no file system, network, processes or timers, and a call must finish within 5 seconds. A function that reads a file or fetches a URL has to go; one that needs another module imports it from the functions directory. JavaScript functions has the contract.

Disable a rule in one place

Spectral turns a rule off for some files with overrides in the ruleset. API Genie turns it off where the issue is, in the document, with x-apigenie-lint-disable on the object and its descendants:

paths:
  /legacy/export:
    x-apigenie-lint-disable:
      - operation-id-defined
    get:
      summary: Export, as it was

Disable a rule as close to the issue as possible; disabling rules says more.

The report in CI

The formats that keep their name: -f json, junit, sarif, html and stylish become --formatter json, junit, sarif, html and stylish. API Genie adds checkstyle, compact, tap, unix and visualstudio; Spectral’s text, pretty, teamcity, github-actions, code-climate, gitlab and markdown have no counterpart. For GitLab, junit is what the merge request reads; for GitHub code scanning, sarif (formatting results).

--fail-severity has no counterpart: the command exits with 1 when an error is reported and with 0 on warnings alone (exit status). To fail the build on a rule, give it error in the configuration. -r ruleset.yaml is --config apigenie.yaml.

On GitHub Actions, stoplightio/spectral-action becomes the GitHub Action, which installs the CLI and shows the issues on the lines of the pull request:

on: pull_request
jobs:
  api:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mykulyak/api-genie-action@v0
        with:
          command: lint
          files: openapi.yaml

Remove Spectral

When the two reports agree on what matters to you: delete .spectral.yaml, remove @stoplight/spectral-cli from the development dependencies, and replace the Spectral step of the pipeline with the one above. The fixes the linter offers are a --fix away:

apigenie lint --fix openapi.yaml
Was this helpful?