ReferenceCLIapigenielint
lint
Detects stylistic and structural issues in OpenAPI, Swagger 2.0 and AsyncAPI
documents, Protobuf (.proto) files and GraphQL schemas, improving their quality.
Usage
apigenie lint [OPTIONS] FILES
Description
The OpenAPI linter helps developers and teams create high-quality, consistent, and well-structured schemas. It achieves this by validating schemas against a set of configurable lint rules.
Lint Rules
Each rule in the linter focuses on a specific aspect of an OpenAPI schema. Here are some examples:
operation-description-defined: Ensures that all operations have meaningful descriptions.operation-success-response: Ensures that all operations define a success response (e.g.201 Created).paths-max-countandpaths-max-depth: Limit API complexity by checking the number of path items and depth (e.g. a number of segments) or URLs.
Each rule can have a severity level assigned, indicating its importance.
Possible values for severity are error, warning and hint (also written
info). All of them show up in the linter output, but only errors prevent the
linter check from succeeding; hints are suggestions, which don’t count as
warnings either. A rule may report a hint where its advice can’t be followed yet,
such as schema-format-deprecated
in OpenAPI 3.0 documents.
Some rules may accept options that adjust how they evaluate the schema.
The API Genie linter currently offers 53 built-in rules for OpenAPI, with over 20 additional rules under active development.
AsyncAPI documents
The linter also checks AsyncAPI 3.x documents; a file is treated as AsyncAPI
when its root object has an asyncapi field. AsyncAPI documents have their own
set of 31 built-in rules, ported from the OpenAPI ones where they make sense
(for example contact-defined, license-defined, operation-tags-defined or
unused-shared-component), plus AsyncAPI-specific ones such as
channel-parameters-defined, channel-addresses-unique and server-hosts.
They come in the same recommended, strict and all rulesets, and support
custom rules, JavaScript functions, x-apigenie-lint-disable and --fix just
like OpenAPI documents. OpenAPI and AsyncAPI files can be linted in a single
run; see lint.asyncapi for their configuration.
Swagger 2.0 documents
The linter also checks Swagger 2.0 documents; a file is treated as Swagger
when its root object has a swagger field. Swagger documents have their own
set of 46 built-in rules, ported from the OpenAPI ones where they make sense
(for example operation-id-unique, parameters-unique,
path-parameters-defined or unused-shared-component), plus Swagger-specific
ones: host-defined, schemes-secure, media-types-defined and
swagger-validation, which reports the violations of the Swagger 2.0
specification that span several objects (undefined security schemes,
unresolved references, several body parameters and so on). They come in the
same recommended, strict and all rulesets, and support custom rules
(whose formats is oas2), JavaScript functions, x-apigenie-lint-disable
and --fix just like OpenAPI documents. See lint.swagger for
their configuration.
GraphQL documents
The linter also checks GraphQL schemas and operations: files with the
.graphql, .graphqls or .gql extension. GraphQL documents have their own
set of 87 built-in rules: the type system and operation rules of the GraphQL
specification, naming conventions, descriptions, deprecations, schema design
(root types, mutation inputs and payloads, unreachable types, identifiers),
the Relay connection conventions, ordering and spelling. Operations are also
checked against a schema: unknown fields, types and arguments, required
arguments, deprecated fields and selection depth. They come in the
recommended, strict, all and relay rulesets. A
# apigenie-lint-disable rule-a, rule-b comment disables rules for a
definition and everything nested in it. --fix does not change GraphQL files.
See lint.graphql for their configuration.
Rulesets
To simplify configuration and provide more flexibility, rules can be combined into rulesets. A ruleset is a named group of rules with assigned severity levels and options.
Disabling certain rules
There might be situations where you want to temporarily disable a rule for a
specific part of a schema. This might be due to a legacy issue, ongoing
development, or a specific API decision. To do this, annotate parts of your
OpenAPI schema with the x-apigenie-lint-disable extension. The value of this
extension must be a list of rule names you want to disable.
Rules can be disabled on any schema object, including OpenAPI object itself.
Disabling a rule on specific schema object applies to that object and its
descendants within the schema hierarchy.
For example, in the snippet below, the
contact-defined rule is disabled on
the Info schema object. However, it will also be enforced on the nested
Contact object because Contact is a descendant of Info within the schema
structure.
info:
x-apigenie-lint-disable:
- contact-defined
A couple of final remarks:
- Disable rules as close to the problematic point in the schema as possible.
- Disabling linter rules should be an exception, not the rule.
Custom rules
Besides the built-in rules, you can define your own rules in the lint
section of the configuration file. A custom rule is data, in the format used
by Spectral: it selects parts of the
document with a JSONPath expression and checks each of them with a
function.
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
A rule definition accepts these properties:
| Property | Description |
|---|---|
given |
JSONPath expression, or a list of them, selecting the values to check. Required. |
then |
What to check: an object or a list of objects with function, optional functionOptions and optional field. Required. |
field |
Narrows each selected value: a member path (summary, a.b[0]), @key to check member names, or a JSONPath expression relative to the value ($.properties[*]). |
message |
Message template; see below. Defaults to description, then to the function’s message. |
description |
Human-readable description of the rule. |
severity |
error, warn (default), info, hint or off. info and hint are reported as hints. |
resolved |
Whether the rule sees the document with local $refs followed. Defaults to true. |
formats |
Restricts the rule to document formats: oas2 (Swagger 2.0 documents), oas3, oas3_0, oas3_1, oas3_2, or, for AsyncAPI documents, aas3 and aas3_0, aas3_1, … |
Messages may use {{error}} (the function’s message), {{description}},
{{path}} (JSON pointer of the problem), {{property}} (the last path
segment) and {{value}}. Issues are reported where the offending value is
defined, even when it is reached through a $ref. Custom rules honour
x-apigenie-lint-disable like built-in ones. A custom rule can be switched off
or have its severity changed later in the configuration (my-rule: off), but
it cannot reuse the name of a built-in rule.
JSONPath
given and field expressions follow
RFC 9535 with the extensions Spectral
rulesets commonly use:
- member unions without quotes:
$.paths[*][get,put]; ~at the end selects member names:$.paths[*]~;- filters written as JavaScript expressions:
$..[?(@.type === 'object' && !@.properties)], with@property,@parent,@parentProperty,@pathand$, and the string and array methodsmatch,test,startsWith,endsWith,includes,indexOf,toLowerCase,toUpperCase,trimandlength.
Script expressions such as [(@.length-1)] and Spectral aliases (#Name)
are not supported.
Core functions
| Function | Options | Checks that the value… |
|---|---|---|
truthy, falsy |
none | is (not) truthy in the JavaScript sense |
defined, undefined |
none | exists / does not exist |
enumeration |
values |
is one of the listed values |
length |
min, max |
has a length (string, array, object keys, number) within bounds |
pattern |
match, notMatch |
(does not) match a JavaScript regular expression; /re/flags is accepted |
casing |
type (flat, camel, pascal, kebab, cobol, snake, macro), disallowDigits, separator.char, separator.allowLeading |
follows a naming convention |
alphabetical |
keyedBy |
lists keys or items in alphabetical order |
or, xor |
properties |
has at least one / exactly one of the listed members |
schema |
schema, dialect, allErrors |
is valid against a JSON Schema (draft 4 to 2020-12) |
typedEnum |
none | has enum values matching its type |
unreferencedReusableObject |
reusableObjectsLocation |
has only members referenced somewhere in the document |
JavaScript functions
When the core functions are not enough, write a function in JavaScript. List
it under functions; API Genie loads <name>.js from the functions
directory next to the configuration file (change it with functionsDir).
commands:
lint:
functions: [operationIdPrefix]
rules:
operation-id-prefix:
given: "$.paths[*][*].operationId"
then:
function: operationIdPrefix
functionOptions: { prefix: api }
A function is an ES module whose default export receives the value, the options and a context, and returns a list of problems. The contract is the one of Spectral custom functions, so most of them work unchanged:
// functions/operationIdPrefix.js
import { createRulesetFunction } from '@stoplight/spectral-core';
import { casing } from '@stoplight/spectral-functions';
export default createRulesetFunction(
{
input: { type: 'string' },
options: {
type: 'object',
properties: { prefix: { type: 'string' } },
required: ['prefix'],
},
},
(input, options, context) => {
if (!input.startsWith(options.prefix)) {
return [{ message: `must start with "${options.prefix}"` }];
}
return casing(input, { type: 'camel' }, context);
},
);
createRulesetFunctionvalidatesfunctionOptionswhen the configuration is loaded and skips values that do not matchinput.- A result may carry a
path(an array of keys and indexes) to point at a more specific location thancontext.path. contextprovidespath,rule(name,severity,resolved),document(formats,data) anddocumentInventory(resolved,unresolved).- Functions may be
async. - A function that throws is reported as an issue of its rule.
Functions run in a sandbox: they can import other modules from the functions
directory and the two built-in @stoplight modules, but have no access to the
file system, network, processes or timers. Each call must finish within 5
seconds. Custom JavaScript functions are not available in the WebAssembly
build.
Autofixing
Some issues found by the apigenie lint command can be automatically fixed.
To do this, run the command with the --fix option. The fixed files keep their
comments, unless --strip-comments is given.
Formatting results
Apart from rules, API Genie linter allows to customize the way it displays results. It ships with several built-in formatters, described below, in subsections.
Formatters differ in the way they present data, but all of them sort issues by:
- The line in the input OpenAPI schema file where they were found
- Severity level: warnings are displayed before errors.
Note: If you used ESLint you may notice that the list of formatters API Genie provides is close to that of ESLint. This is by no coincidence, as both tools aim to be usable in many different environments.
checkstyle
Outputs results in the Checkstyle format.
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="/apigenie/data/openapi.yaml">
<error line="2" column="2" severity="error" message="Info summary is missing or empty" source="info-defined"/>
<error line="12" column="4" severity="warning" message="Tag 'Articles' has no meaningful description defined" source="tag-description-defined"/>
<error line="13" column="4" severity="warning" message="Tag 'Comments' has no meaningful description defined" source="tag-description-defined"/>
<error line="14" column="4" severity="warning" message="Tag 'Favorites' has no meaningful description defined" source="tag-description-defined"/>
<error line="15" column="4" severity="warning" message="Tag 'Profile' has no meaningful description defined" source="tag-description-defined"/>
</file>
</checkstyle>
compact
Mimicks the default output of JSHint.
/apigenie/data/openapi.yaml: line 2, col 2, Error - Info summary is missing or empty. (info-defined)
/apigenie/data/openapi.yaml: line 12, col 4, Warning - Tag 'Articles' has no meaningful description defined. (tag-description-defined)
/apigenie/data/openapi.yaml: line 13, col 4, Warning - Tag 'Comments' has no meaningful description defined. (tag-description-defined)
/apigenie/data/openapi.yaml: line 14, col 4, Warning - Tag 'Favorites' has no meaningful description defined. (tag-description-defined)
/apigenie/data/openapi.yaml: line 15, col 4, Warning - Tag 'Profile' has no meaningful description defined. (tag-description-defined)
html
Prints results as a single HTML file. Issues are displayed in a table, every issue takes one table row.
<!DOCTYPE html>
<html>
<body>
<section id="overview">
<h1>API Genie Report</h1>
<ul>
<li>Total: 224</li>
<li>Errors: 32</li>
<li>Warnings: 192</li>
</ul>
</section>
<section id="issues">
<table>
<caption>/apigenie/data/openapi.yaml</caption>
<tr>
<td class="pos">2:2</td>
<td class="error">Error</td>
<td>Info summary is missing or empty</td>
<td>
<a href="https://apigenie.pl/docs/linter/openapi/rules/info-defined">info-defined</a>
</td>
</tr>
<tr>
<td class="pos">12:4</td>
<td class="warning">Warning</td>
<td>Tag 'Articles' has no meaningful description defined</td>
<td>
<a href="https://apigenie.pl/docs/linter/openapi/rules/tag-description-defined">tag-description-defined</a>
</td>
</tr>
</table>
</body>
</html>
json
Outputs lint results in JSON file. This format is useful if you want to process
linter result programmatically. results has one item per file with issues;
formatVersion is the version of the format, which changes only when a member is
removed or changes meaning (see Compatibility).
Its JSON Schema is
apigenie-lint-report.json.
{
"formatVersion": 1,
"results": [
{
"filePath": "/apigenie/data/openapi.yaml",
"messages": [
{
"column": 2,
"line": 2,
"message": "Info summary is missing or empty",
"ruleId": "info-defined",
"severity": "error"
},
{
"column": 4,
"line": 12,
"message": "Tag 'Articles' has no meaningful description defined",
"ruleId": "tag-description-defined",
"severity": "warning"
},
... more issues ...
]
}
]
}
API Genie 0.9 and earlier wrote the bare results array.
junit
Outputs results to format compatible with the JUnit Jenkins plugin.
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite package="apigenie" time="0" tests="224" errors="224" name="/apigenie/data/openapi.yaml">
<testcase time="0" name="apigenie.info-defined" classname="apigenie.info-defined">
<failure message="Info summary is missing or empty"><![CDATA[line 2, col 2, Error - Info summary is missing or empty. (info-defined)]]></failure>
</testcase>
<testcase time="0" name="apigenie.tag-description-defined" classname="apigenie.tag-description-defined">
<failure message="Tag 'Articles' has no meaningful description defined"><![CDATA[line 12, col 4, Warning - Tag 'Articles' has no meaningful description defined. (tag-description-defined)]]></failure>
</testcase>
<testcase time="0" name="apigenie.tag-description-defined" classname="apigenie.tag-description-defined">
<failure message="Tag 'Comments' has no meaningful description defined"><![CDATA[line 13, col 4, Warning - Tag 'Comments' has no meaningful description defined. (tag-description-defined)]]></failure>
</testcase>
</testsuite>
</testsuites>
sarif
Outputs results in SARIF 2.1.0,
the format of GitHub code scanning and other static analysis dashboards. Files below
the current directory are given relative to it (%SRCROOT%), so that code scanning
finds them in the repository. A result whose JSON pointer is known has it as a
logical location and in a fingerprint, which lets code scanning keep tracking the
issue when lines move.
{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "apigenie",
"informationUri": "https://apigenie.pl",
"rules": [
{
"id": "info-defined",
"helpUri": "https://apigenie.pl/docs/linter/openapi/rules/info-defined"
},
... more rules ...
]
}
},
"originalUriBaseIds": {
"%SRCROOT%": { "uri": "file:///apigenie/" }
},
"results": [
{
"ruleId": "info-defined",
"ruleIndex": 0,
"level": "error",
"message": { "text": "Info summary is missing or empty" },
"locations": [
{
"physicalLocation": {
"artifactLocation": { "uri": "data/openapi.yaml", "uriBaseId": "%SRCROOT%" },
"region": { "startLine": 2, "startColumn": 2 }
},
"logicalLocations": [
{ "fullyQualifiedName": "#/info", "kind": "member" }
]
}
],
"partialFingerprints": {
"apigenieLocation/v1": "info-defined|data/openapi.yaml|#/info"
}
},
... more issues ...
]
}
]
}
stylish
This is the default formatter. It presents result as table, with values being highligted in different colors.
764:6 warning Unused shared component unused-shared-component
769:12 warning Object schema does not define constraints restricting its size schema-constraints
769:12 error Object schema does not disable additional properties schema-no-additional-properties
777:6 warning Unused shared component unused-shared-component
781:8 error Schema with integer type does not define proper format schema-format-defined
781:8 warning Integer schema does not define constraints restricting its range schema-constraints
785:6 warning Unused shared component unused-shared-component
789:8 error Schema with integer type does not define proper format schema-format-defined
789:8 warning Integer schema does not define constraints restricting its range schema-constraints
795:6 warning Unused shared component unused-shared-component
224 issues (32 errors, 192 warnings)
0 errors, 6 warnings are potentially auto-fixable
tap
Outputs results to the Test Anything Protocol (TAP) specification format.
TAP version 14
1..1
not ok - /apigenie/data/openapi.yaml
---
errorCount: 32
issues:
- column: 2
line: 2
message: Info summary is missing or empty
ruleId: info-defined
severity: error
- column: 4
line: 12
message: Tag 'Articles' has no meaningful description defined
ruleId: tag-description-defined
severity: warning
- column: 4
line: 13
message: Tag 'Comments' has no meaningful description defined
ruleId: tag-description-defined
severity: warning
unix
Outputs results to a format similar to many commands in UNIX-like systems.
If you use grep or awk, this format may be best for you.
/apigenie/data/openapi.yaml:2:2: Info summary is missing or empty. [Error/info-defined]
/apigenie/data/openapi.yaml:12:4: Tag 'Articles' has no meaningful description defined. [Warning/tag-description-defined]
/apigenie/data/openapi.yaml:13:4: Tag 'Comments' has no meaningful description defined. [Warning/tag-description-defined]
/apigenie/data/openapi.yaml:14:4: Tag 'Favorites' has no meaningful description defined. [Warning/tag-description-defined]
/apigenie/data/openapi.yaml:15:4: Tag 'Profile' has no meaningful description defined. [Warning/tag-description-defined]
/apigenie/data/openapi.yaml:16:4: Tag 'Tags' has no meaningful description defined. [Warning/tag-description-defined]
visualstudio
Outputs results to format compatible with the integrated terminal of the Visual Studio IDE.
/apigenie/data/openapi.yaml(2,2): error info-defined : Info summary is missing or empty.
/apigenie/data/openapi.yaml(12,4): warning tag-description-defined : Tag 'Articles' has no meaningful description defined.
/apigenie/data/openapi.yaml(13,4): warning tag-description-defined : Tag 'Comments' has no meaningful description defined.
/apigenie/data/openapi.yaml(14,4): warning tag-description-defined : Tag 'Favorites' has no meaningful description defined.
/apigenie/data/openapi.yaml(15,4): warning tag-description-defined : Tag 'Profile' has no meaningful description defined.
/apigenie/data/openapi.yaml(16,4): warning tag-description-defined : Tag 'Tags' has no meaningful description defined.
Options
–config FILE
Optional path to the configuration file. If not specified, the default configuration is used. For details, see the configuration section.
–fix
If possible, automatically fixes problems it finds. Modified file is written under the same name as the original. The exit status reflects the issues that remain after the fixes. The YAML comments of the fixed OpenAPI, Swagger and AsyncAPI documents are kept with the values they belong to; those of values a fix removes go with them. Protobuf files are fixed by editing their text, which keeps everything outside the edited declarations.
–strip-comments
With --fix, drops the YAML comments of the OpenAPI, Swagger and AsyncAPI
documents it rewrites, instead of keeping them. Files without fixes are not
rewritten.
–formatter NAME
Sets the formatter to display the linter result. Supported formatter names are:
checkstylecompacthtmljsonjunitsarifstylish- the default formattertapunixvisualstudio
–graphql-schema FILE
A GraphQL schema file that the operations and fragments of the linted GraphQL
files are checked against. Can be repeated for a schema split across files.
When given, it replaces the files of the schema option of the
configuration; relative paths resolve against the current directory. With
--watch, the schema files are watched too.
apigenie lint --graphql-schema schema.graphql queries/*.graphql
Exit status
The command exits with 0 when no issue of severity error is reported, even if
warnings or hints are, and with 1 when at least one is. With --fix, only the issues
that remain after the fixes count. It exits with 2 when the configuration or an
input file can’t be read or parsed, or an input is not a document of its
specification. With --watch the command keeps running and never exits on
issues. See the exit statuses shared by the three binaries.
Configuration
The linter stores its configuration under the #/commands/linter key in the
configuration file. See the global configuration
section to understand how this file is structured.
The linter configuration allows to apply rulesets, enable or disable certain rules, modify rule severity levels, or modify rules’ options.
Example: a minimal confiration illustrating all possible variants of configuring rules.
version: '1.0'
commands:
lint:
extends:
- recommended
# ... more rulesets to extend from ...
rules:
info-defined: off
paths-max-count:
- error
- 30
schema-datetime-suffix:
- error
- At
tags-unique: error
# ... more rules ...
If the configuration file is not specified, linter uses the default configuration:
version: '1.0'
commands:
lint:
extends:
- recommended
lint.extends
This block lists rulesets whose configuration will be used and combined to create the final set of rules, rule severity levels and options.
The value of this block, if present, must be an array of supported ruleset names. Linter will read all rulesets’ configurations, in the order they are listed. If two rulesets contain the same rule, linter uses the configuration from the ruleset that was specified later in the list.
lint.rules
These are overrides to the rules’ configurations from rulesets.
If present, value of this block must be a map. Keys of the map must be valid rule names, and values can be either strings or two-element arrays.
For valid rule names see the rule index page.
lint.rules.RULE_ID
Such blocks configure a rule named RULE_ID. Each block may be a string or a
two-element array.
- String: If it a string, and must be one of:
off- to disable rulewarning- to set rule severity to warningerror- to set rule severity to error
- Array: If it is an array, it must contain two elements:
- The first element must be a valid rule severity (either
warningorerror). - The second element is rule-specific value, containing rule’s options.
- The first element must be a valid rule severity (either
See the documentation of each rule to find what options it accepts.
Examples
- Disables the
info-definedrule:
version: '1.0'
commands:
lint:
rules:
info-defined: off
- configure the
paths-max-countrule to issue an error when the number of path items is larger than 30. If the number is below or equal to 30, the rule does not produce an error or warning.
version: '1.0'
commands:
lint:
rules:
paths-max-count:
- error
- 30
lint.asyncapi
The configuration of the linter for AsyncAPI documents. It accepts the same
keys as the lint block itself (extends, rules, functions and
functionsDir), with rule names of the AsyncAPI rules. The OpenAPI settings
of the lint block do not apply to AsyncAPI documents; without this block,
AsyncAPI documents are linted with the recommended ruleset.
version: '1.0'
commands:
lint:
extends: [recommended]
asyncapi:
extends: [strict]
rules:
parameter-naming: [warning, camel]
server-hosts: off
lint.swagger
The configuration of the linter for Swagger 2.0 documents. It accepts the same
keys as the lint block itself (extends, rules, functions and
functionsDir), with rule names of the Swagger rules. The OpenAPI settings of
the lint block do not apply to Swagger documents; without this block,
Swagger documents are linted with the recommended ruleset.
version: '1.0'
commands:
lint:
extends: [recommended]
swagger:
extends: [strict]
rules:
schemes-secure: [error, { upgradeInsecure: true }]
swagger-validation: [error, { checks: [unresolved-reference, security-scheme-defined] }]
paths-sorted: off
lint.graphql
The configuration of the linter for GraphQL documents. It accepts extends,
rules, self_contained (whether each document holds every type and fragment
it references, so that references to missing ones are reported) and schema
(a path, or a list of paths, of the schema files operations are checked
against; relative paths resolve against the directory of the configuration
file; –graphql-schema replaces them). Without schema, operations are checked against the type system of
their own document. Without this block, GraphQL documents are linted with the
recommended ruleset.
version: '1.0'
commands:
lint:
graphql:
extends: [recommended, relay]
schema: schema.graphql
rules:
field-description-defined: warning
selection-set-depth: [error, { maxDepth: 5 }]
no-deprecated: off
See Also
- Rule index lists all available rules.
- Ruleset index lists all available rulesets.