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
extendsnames API Genie’s rulesets, not Spectral’s.recommendedis the default;strictturns on more rules with higher severities,allevery rule. The rulesets of each standard are listed in the linter rules.- A rule takes its name from the table below. Spectral’s
warniswarningfor a built-in rule;hintandoffstay. - 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.
What the recommended ruleset adds
Rules of recommended that spectral:oas has nothing for; each has a page that says what it
checks and why:
- schema-format-defined, schema-format-type-mismatch and schema-format-deprecated: numbers and strings say their format, and the format fits the type.
- schema-constraints and schema-no-additional-properties: strings, numbers, arrays and objects have bounds.
- schema-no-contradictions,
schema-oneof-no-overlap and
schema-conditional-effective: schemas
that reject every value,
oneOfbranches a value matches twice,ifwithout effect. - standard-response-codes, unambiguous-response-codes and no-content-response-defined: the responses an operation declares.
- paths-max-depth, paths-max-count, parameter-naming and schema-datetime-suffix: the shape of the URLs and the names.
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,thenwithfield,functionandfunctionOptions,messagewith{{error}},{{description}},{{path}},{{property}}and{{value}},description,severity,resolvedandformats(oas2,oas3,oas3_0,oas3_1,oas3_2,aas3, …).- The core functions:
truthy,falsy,defined,undefined,enumeration,length,pattern,casing,alphabetical,or,xor,schema,typedEnumandunreferencedReusableObject, 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,@pathand the string methods (JSONPath).
What does not:
aliasesand the#Namereferences to them: write the path out.- Script expressions such as
[(@.length-1)]. extendswith 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,documentationUrlandexcept.- 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