zuplo 7.13.4 → 7.13.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/articles/api-lint-breaking-changes.mdx +211 -0
- package/docs/articles/api-lint-ci-cd.mdx +242 -0
- package/docs/articles/api-lint-configuration.mdx +459 -0
- package/docs/articles/api-lint-custom-rules.mdx +222 -0
- package/docs/articles/api-lint-rules.mdx +360 -0
- package/docs/articles/api-lint.mdx +267 -0
- package/docs/cli/lint.mdx +64 -0
- package/docs/cli/lint.partial.mdx +70 -0
- package/docs/dev-portal/zudoku/configuration/api-catalog.md +16 -0
- package/docs/dev-portal/zudoku/configuration/blog.md +217 -0
- package/package.json +5 -5
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Lint Rules Reference
|
|
3
|
+
sidebar_label: Rules reference
|
|
4
|
+
description:
|
|
5
|
+
Every rule `zuplo lint` runs, which presets turn it on, its default severity,
|
|
6
|
+
example output, and how to fix the OpenAPI file or policy that triggers it.
|
|
7
|
+
applies_to:
|
|
8
|
+
product: gateway
|
|
9
|
+
hosting: all
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Each rule has an ID, a default severity, and the presets that include it. Use
|
|
13
|
+
the ID to change the rule's severity, limit it to some routes, or turn it off in
|
|
14
|
+
[`zuplo.lint.jsonc`](./api-lint-configuration.mdx#rules). To add rules of your
|
|
15
|
+
own, see [Custom lint rules](./api-lint-custom-rules.mdx).
|
|
16
|
+
|
|
17
|
+
## Rules by preset
|
|
18
|
+
|
|
19
|
+
| Rule | Severity | recommended | strict | security | Auto-fix |
|
|
20
|
+
| -------------------------------------------------------------------- | ---------- | :---------: | :----: | :------: | :------: |
|
|
21
|
+
| [`operation-operationId`](#operation-operationid) | error | ✓ | ✓ | ✓ | ✓ |
|
|
22
|
+
| [`operation-description`](#operation-description) | warn | ✓ | ✓ | ✓ | |
|
|
23
|
+
| [`operation-tags`](#operation-tags) | warn | ✓ | ✓ | ✓ | |
|
|
24
|
+
| [`path-params`](#path-params) | error | ✓ | ✓ | ✓ | |
|
|
25
|
+
| [`no-ambiguous-paths`](#no-ambiguous-paths) | error | ✓ | ✓ | ✓ | |
|
|
26
|
+
| [`zuplo/no-undefined-policies`](#zuplono-undefined-policies) | error | ✓ | ✓ | ✓ | |
|
|
27
|
+
| [`zuplo/no-empty-inbound-policies`](#zuplono-empty-inbound-policies) | warn | ✓ | ✓ | ✓ | ✓ |
|
|
28
|
+
| [`zuplo/require-route-label`](#zuplorequire-route-label) | warn | ✓ | ✓ | ✓ | |
|
|
29
|
+
| [`zuplo/schema-policies`](#zuploschema-policies) | error | ✓ | ✓ | ✓ | |
|
|
30
|
+
| [`zuplo/schema-policy-options`](#zuploschema-policy-options) | warn | ✓ | ✓ | ✓ | |
|
|
31
|
+
| [`zuplo/schema-x-zuplo-route`](#zuploschema-x-zuplo-route) | error | ✓ | ✓ | ✓ | |
|
|
32
|
+
| [`zuplo/require-policy`](#zuplorequire-policy) | per policy | | ✓ | ✓ | |
|
|
33
|
+
| [`zuplo/require-cors-policy`](#zuplorequire-cors-policy) | error | | ✓ | | |
|
|
34
|
+
| [`info-description`](#info-description) | warn | | ✓ | | |
|
|
35
|
+
| [`info-contact`](#info-contact) | warn | | ✓ | | |
|
|
36
|
+
| [`no-http-verbs-in-path`](#no-http-verbs-in-path) | error | | ✓ | | |
|
|
37
|
+
|
|
38
|
+
Two more IDs can appear in results without being rules you configure:
|
|
39
|
+
|
|
40
|
+
- `breaking-change` — reported by
|
|
41
|
+
[breaking change detection](./api-lint-breaking-changes.mdx).
|
|
42
|
+
- `lint-execution-error` — a rule failed to run against a file. This usually
|
|
43
|
+
means the file isn't valid JSON or isn't valid OpenAPI, so it has no line
|
|
44
|
+
number (`0:0`). Fix the file and run the linter again.
|
|
45
|
+
|
|
46
|
+
## General OpenAPI rules
|
|
47
|
+
|
|
48
|
+
The Zuplo presets also include these general OpenAPI rules. Each one catches a
|
|
49
|
+
real defect and stays quiet on a typical Zuplo project.
|
|
50
|
+
|
|
51
|
+
| Rule | Severity | Presets | Reports |
|
|
52
|
+
| ------------------------------------ | -------- | ------- | ----------------------------------------------------------------- |
|
|
53
|
+
| `duplicate-paths` | error | all | The same path defined twice; only the last definition is kept. |
|
|
54
|
+
| `operation-operationId-unique` | error | all | Two operations with the same `operationId`. |
|
|
55
|
+
| `operation-parameters` | error | all | The same parameter declared twice on an operation. |
|
|
56
|
+
| `path-declarations-must-exist` | error | all | An empty path parameter, such as `/users/{}`. |
|
|
57
|
+
| `path-not-include-query` | error | all | A query string in a path key. |
|
|
58
|
+
| `oas3-operation-security-defined` | error | all | A `security` entry with no matching `securitySchemes` definition. |
|
|
59
|
+
| `oas-schema-check` | error | all | A schema with an invalid `type`, such as `"string "`. |
|
|
60
|
+
| `no-eval-in-markdown` | error | all | `eval()` in a Markdown description. |
|
|
61
|
+
| `no-script-tags-in-markdown` | error | all | `<script>` in a Markdown description. |
|
|
62
|
+
| `operation-operationId-valid-in-url` | warn | all | An `operationId` with characters that aren't URL-friendly. |
|
|
63
|
+
| `operation-success-response` | warn | all | An operation without a `2xx` or `3xx` response. |
|
|
64
|
+
| `oas3-valid-schema-example` | warn | all | An example that doesn't match its schema. |
|
|
65
|
+
| `typed-enum` | warn | all | An enum value that doesn't match the enum's type. |
|
|
66
|
+
| `duplicated-entry-in-enum` | warn | all | A repeated enum value. |
|
|
67
|
+
| `nullable-enum-contains-null` | warn | all | A nullable enum without `null` in its values. |
|
|
68
|
+
| `path-keys-no-trailing-slash` | warn | all | A path ending in `/`. |
|
|
69
|
+
| `no-request-body` | warn | all | A request body on a `GET` or `DELETE` operation. |
|
|
70
|
+
| `operation-4xx-response` | warn | strict | An operation without a `4xx` error response. |
|
|
71
|
+
| `oas3-parameter-description` | warn | strict | A parameter without a description. |
|
|
72
|
+
| `component-description` | warn | strict | A component without a description. |
|
|
73
|
+
| `operation-tag-defined` | warn | strict | An operation tag missing from the top-level `tags` list. |
|
|
74
|
+
| `oas3-unused-component` | warn | strict | A component nothing references. |
|
|
75
|
+
| `required-fields-defined` | warn | strict | A `required` property that isn't declared in `properties`. |
|
|
76
|
+
| `no-unnecessary-combinator` | warn | strict | An `allOf`, `anyOf`, or `oneOf` with a single item. |
|
|
77
|
+
|
|
78
|
+
These rules come from vacuum, the engine behind `zuplo lint`. To use vacuum's
|
|
79
|
+
other general OpenAPI rules, or its OWASP security rules, extend `vacuum:oas` or
|
|
80
|
+
`spectral:owasp`; see [`extends`](./api-lint-configuration.mdx#extends). Run
|
|
81
|
+
`npx zuplo lint --list-rules` to see every rule and which rulesets include it.
|
|
82
|
+
|
|
83
|
+
## OpenAPI rules
|
|
84
|
+
|
|
85
|
+
These rules check the OpenAPI document itself. They apply to every operation,
|
|
86
|
+
whether or not it has an `x-zuplo-route` block.
|
|
87
|
+
|
|
88
|
+
### `operation-operationId`
|
|
89
|
+
|
|
90
|
+
Every operation must have an `operationId`. Zuplo uses it to identify the route,
|
|
91
|
+
and code generators and the Developer Portal use it to name operations.
|
|
92
|
+
|
|
93
|
+
```txt
|
|
94
|
+
6:14 error Every operation must have an operationId: `operationId` must be set operation-operationId
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Fix:** Add a unique `operationId` to the operation, or run `zuplo lint --fix`
|
|
98
|
+
to generate one from the method and path:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
"/todos/{id}": {
|
|
102
|
+
"delete": {
|
|
103
|
+
"operationId": "deleteTodosById",
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `operation-description`
|
|
107
|
+
|
|
108
|
+
Operations should have a `description`. The Developer Portal shows it as the
|
|
109
|
+
operation's documentation.
|
|
110
|
+
|
|
111
|
+
```txt
|
|
112
|
+
6:14 warn Operations should have descriptions: `description` must be set operation-description
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Fix:** Add a `description` that explains what the operation does.
|
|
116
|
+
|
|
117
|
+
### `operation-tags`
|
|
118
|
+
|
|
119
|
+
Operations should have at least one tag. The Developer Portal groups operations
|
|
120
|
+
by tag.
|
|
121
|
+
|
|
122
|
+
```txt
|
|
123
|
+
6:14 warn Operations should have tags: `tags` must be set operation-tags
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Fix:** Add a `tags` array, for example `"tags": ["Todos"]`.
|
|
127
|
+
|
|
128
|
+
### `path-params`
|
|
129
|
+
|
|
130
|
+
Every parameter in a path template must be declared, either on the path or on
|
|
131
|
+
the operation. The rule also reports two paths that differ only in the names of
|
|
132
|
+
their parameters.
|
|
133
|
+
|
|
134
|
+
```txt
|
|
135
|
+
19:5 error parameter named `id` must be defined as part of the path `/todos/{id}` definition, or in the `DELETE` operation(s) path-params
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Fix:** Declare the parameter with `"in": "path"` and `"required": true`:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
"/todos/{id}": {
|
|
142
|
+
"delete": {
|
|
143
|
+
"parameters": [
|
|
144
|
+
{
|
|
145
|
+
"name": "id",
|
|
146
|
+
"in": "path",
|
|
147
|
+
"required": true,
|
|
148
|
+
"schema": { "type": "string" }
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `no-ambiguous-paths`
|
|
154
|
+
|
|
155
|
+
Two paths can't match the same request. `/users/{id}` and `/users/{name}` are
|
|
156
|
+
ambiguous: a request to `/users/42` matches both.
|
|
157
|
+
|
|
158
|
+
```txt
|
|
159
|
+
6:5 error paths are ambiguous with one another: `/users/{id}` (GET) and `/users/{name}` (GET) no-ambiguous-paths
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This pair is also reported by [`path-params`](#path-params), so turning off one
|
|
163
|
+
of the two rules doesn't silence it.
|
|
164
|
+
|
|
165
|
+
**Fix:** Merge the two paths into one, or change one so it no longer overlaps,
|
|
166
|
+
for example `/users/by-name/{name}`.
|
|
167
|
+
|
|
168
|
+
### `info-description`
|
|
169
|
+
|
|
170
|
+
_`zuplo:strict` only._ The document's `info` object must have a `description`.
|
|
171
|
+
|
|
172
|
+
```txt
|
|
173
|
+
3:11 warn API info must have a description: `description` must be set info-description
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Fix:** Add `info.description`.
|
|
177
|
+
|
|
178
|
+
### `info-contact`
|
|
179
|
+
|
|
180
|
+
_`zuplo:strict` only._ The document's `info` object should have a `contact`.
|
|
181
|
+
|
|
182
|
+
```txt
|
|
183
|
+
3:11 warn API info should have contact info: `contact` must be set info-contact
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Fix:** Add `info.contact`, for example
|
|
187
|
+
`"contact": { "name": "API Support", "email": "api@example.com" }`.
|
|
188
|
+
|
|
189
|
+
### `no-http-verbs-in-path`
|
|
190
|
+
|
|
191
|
+
_`zuplo:strict` only._ Path segments shouldn't be HTTP methods. The HTTP method
|
|
192
|
+
already says what the operation does.
|
|
193
|
+
|
|
194
|
+
```txt
|
|
195
|
+
7:19 error path `/users/get` contains an HTTP Verb `get` no-http-verbs-in-path
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The rule matches whole segments, so `/users/get` and `/post` are reported, while
|
|
199
|
+
`/getUsers` and `/delete-user` aren't.
|
|
200
|
+
|
|
201
|
+
**Fix:** Move the action into the method: `GET /users` instead of
|
|
202
|
+
`GET /users/get`.
|
|
203
|
+
|
|
204
|
+
## Zuplo route rules
|
|
205
|
+
|
|
206
|
+
These rules check the `x-zuplo-route` block of each operation. Operations
|
|
207
|
+
without an `x-zuplo-route` block are skipped.
|
|
208
|
+
|
|
209
|
+
### `zuplo/no-undefined-policies`
|
|
210
|
+
|
|
211
|
+
Every policy name in a route's `policies.inbound` or `policies.outbound` list
|
|
212
|
+
must be defined in `config/policies.json`. A missing policy fails the build.
|
|
213
|
+
|
|
214
|
+
```txt
|
|
215
|
+
15:56 error Policy "rate-limit-inbound" is referenced in route but not defined in policies.json zuplo/no-undefined-policies
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Fix:** Correct the spelling of the policy name in the route, or add the policy
|
|
219
|
+
to `config/policies.json`. The rule only runs when `config/policies.json`
|
|
220
|
+
defines at least one policy.
|
|
221
|
+
|
|
222
|
+
### `zuplo/no-empty-inbound-policies`
|
|
223
|
+
|
|
224
|
+
A route shouldn't have an empty `policies.inbound` array. An empty array has the
|
|
225
|
+
same effect as no array, and it's usually left behind after removing a policy.
|
|
226
|
+
|
|
227
|
+
```txt
|
|
228
|
+
31:36 warn Route delete /todos/{id} has an empty inbound policies array zuplo/no-empty-inbound-policies
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Fix:** Remove the empty array, or run `zuplo lint --fix`.
|
|
232
|
+
|
|
233
|
+
### `zuplo/require-route-label`
|
|
234
|
+
|
|
235
|
+
Routes should have a `label` in `x-zuplo-route`. The label names the route in
|
|
236
|
+
the Zuplo Portal's route designer, logs, and analytics.
|
|
237
|
+
|
|
238
|
+
```txt
|
|
239
|
+
6:14 warn Route get /todos should have a label in x-zuplo-route zuplo/require-route-label
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Fix:** Add `"label": "List todos"` to the `x-zuplo-route` block.
|
|
243
|
+
|
|
244
|
+
### `zuplo/require-policy`
|
|
245
|
+
|
|
246
|
+
_`zuplo:strict` and `zuplo:security`, or any preset with
|
|
247
|
+
[`requiredPolicies`](./api-lint-configuration.mdx#requiredpolicies)._ Every
|
|
248
|
+
route must include a policy of each required type.
|
|
249
|
+
|
|
250
|
+
| Preset | Authentication | Rate limiting |
|
|
251
|
+
| ---------------- | -------------- | ------------- |
|
|
252
|
+
| `zuplo:strict` | error | warn |
|
|
253
|
+
| `zuplo:security` | error | error |
|
|
254
|
+
|
|
255
|
+
```txt
|
|
256
|
+
27:14 error Routes must have an authentication policy: get /health zuplo/require-policy
|
|
257
|
+
9:14 warn Routes should have a rate limiting policy: get /users zuplo/require-policy-warn
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Requirements at warning severity report as `zuplo/require-policy-warn`.
|
|
261
|
+
Configure both through `zuplo/require-policy`: setting it in `rules` or
|
|
262
|
+
`overrides` applies to every requirement. `zuplo/require-policy-warn` isn't a
|
|
263
|
+
rule ID you can configure.
|
|
264
|
+
|
|
265
|
+
**Fix:** Add an authentication policy, such as
|
|
266
|
+
[API key authentication](../policies/api-key-inbound.mdx), or a
|
|
267
|
+
[rate limiting policy](../policies/rate-limit-inbound.mdx) to the route's
|
|
268
|
+
`policies.inbound` list. For routes that are intentionally public, such as a
|
|
269
|
+
health check, turn the rule off for them with an
|
|
270
|
+
[override](./api-lint-configuration.mdx#overrides):
|
|
271
|
+
|
|
272
|
+
```jsonc
|
|
273
|
+
{
|
|
274
|
+
"version": 2,
|
|
275
|
+
"overrides": [
|
|
276
|
+
{ "paths": ["/health"], "rules": { "zuplo/require-policy": "off" } },
|
|
277
|
+
],
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
To change which policy types count, set your own
|
|
282
|
+
[`requiredPolicies`](./api-lint-configuration.mdx#requiredpolicies). To require
|
|
283
|
+
a policy on only some routes, use a
|
|
284
|
+
[custom rule](./api-lint-custom-rules.mdx#require-a-policy-on-some-routes).
|
|
285
|
+
|
|
286
|
+
### `zuplo/require-cors-policy`
|
|
287
|
+
|
|
288
|
+
_`zuplo:strict` only._ Every route must set `corsPolicy` in `x-zuplo-route`, so
|
|
289
|
+
the CORS behavior of each route is a deliberate choice.
|
|
290
|
+
|
|
291
|
+
```txt
|
|
292
|
+
62:14 error Route get /no-cors should have a corsPolicy in x-zuplo-route zuplo/require-cors-policy
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Fix:** Set `corsPolicy` to the name of a [custom CORS policy](./cors.mdx), to
|
|
296
|
+
`"anything-goes"`, or to `"none"`. Any value satisfies the rule, including
|
|
297
|
+
`"none"`.
|
|
298
|
+
|
|
299
|
+
## Schema rules
|
|
300
|
+
|
|
301
|
+
These rules validate your configuration against the same JSON schemas Zuplo uses
|
|
302
|
+
at build time. They're part of every preset.
|
|
303
|
+
|
|
304
|
+
### `zuplo/schema-policies`
|
|
305
|
+
|
|
306
|
+
Validates the structure of `config/policies.json`: each policy has a `name`,
|
|
307
|
+
`policyType`, and `handler`, and each name uses only lowercase letters, numbers,
|
|
308
|
+
and dashes.
|
|
309
|
+
|
|
310
|
+
```txt
|
|
311
|
+
4:7 error Schema validation error at /policies/0/name: at '/policies/0/name': 'INVALID_NAME' does not match pattern '^[0-9a-z-]+$' zuplo/schema-policies
|
|
312
|
+
15:6 error Schema validation error at /policies/1: at '/policies/1': missing property 'handler' zuplo/schema-policies
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Fix:** Correct the field the message points to. The path after
|
|
316
|
+
`Schema validation error at` is the location in the file, so `/policies/0/name`
|
|
317
|
+
is the `name` of the first policy.
|
|
318
|
+
|
|
319
|
+
### `zuplo/schema-policy-options`
|
|
320
|
+
|
|
321
|
+
Validates `handler.options` of each built-in policy against that policy's
|
|
322
|
+
options. This catches missing required options, wrong types, and values outside
|
|
323
|
+
an allowed list. The rule reports warnings by default, so these results don't
|
|
324
|
+
fail a run unless you set `failOn` to `"warn"` or raise the rule to `"error"`.
|
|
325
|
+
|
|
326
|
+
```txt
|
|
327
|
+
17:44 warn Schema validation error at /policies/1/handler/options: at '/policies/1/handler/options': missing properties 'rateLimitBy', 'requestsAllowed', 'timeWindowMinutes' zuplo/schema-policy-options
|
|
328
|
+
10:11 warn Schema validation error at /policies/0/handler/options/rateLimitBy: at '/policies/0/handler/options/rateLimitBy': value must be one of 'user', 'ip', 'function', 'all' zuplo/schema-policy-options
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Custom code policies and policy types the linter doesn't recognize are skipped,
|
|
332
|
+
so they never produce results from this rule.
|
|
333
|
+
|
|
334
|
+
**Fix:** Compare the options with the policy's page in the
|
|
335
|
+
[policy catalog](../policies/overview.mdx).
|
|
336
|
+
|
|
337
|
+
### `zuplo/schema-x-zuplo-route`
|
|
338
|
+
|
|
339
|
+
Validates each `x-zuplo-route` block in your OpenAPI files: it must have a
|
|
340
|
+
`handler`, policy names must use valid characters, and it can't contain
|
|
341
|
+
properties Zuplo doesn't recognize.
|
|
342
|
+
|
|
343
|
+
```txt
|
|
344
|
+
12:26 error Schema validation error at : at '': additional properties 'unknownProperty' not allowed zuplo/schema-x-zuplo-route
|
|
345
|
+
30:26 error Schema validation error at : at '': missing property 'handler' zuplo/schema-x-zuplo-route
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
An empty location (`at ''`) means the problem is in the `x-zuplo-route` object
|
|
349
|
+
itself.
|
|
350
|
+
|
|
351
|
+
**Fix:** Remove or rename the unrecognized property, or add the missing one. A
|
|
352
|
+
misspelled property such as `corsPolicies` shows up here as an additional
|
|
353
|
+
property. See [x-zuplo-route](./openapi.mdx#x-zuplo-route) for the properties it
|
|
354
|
+
accepts.
|
|
355
|
+
|
|
356
|
+
## Next steps
|
|
357
|
+
|
|
358
|
+
- [Lint configuration](./api-lint-configuration.mdx) — change severities, pick a
|
|
359
|
+
preset, and require policies.
|
|
360
|
+
- [Lint in CI/CD](./api-lint-ci-cd.mdx) — run these rules on every pull request.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Linting Your API
|
|
3
|
+
sidebar_label: Overview
|
|
4
|
+
description:
|
|
5
|
+
Use `zuplo lint` to check OpenAPI files and policies for missing fields,
|
|
6
|
+
undefined policies, invalid options, and breaking changes before you deploy a
|
|
7
|
+
gateway.
|
|
8
|
+
applies_to:
|
|
9
|
+
product: gateway
|
|
10
|
+
hosting: all
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A Zuplo gateway is mostly configuration: OpenAPI files in `config/` describe
|
|
14
|
+
your routes, and `config/policies.json` defines the policies those routes run. A
|
|
15
|
+
typo in that configuration — a policy name that doesn't exist, a required option
|
|
16
|
+
left out, a route with no authentication — doesn't fail a type check. It shows
|
|
17
|
+
up later, as a failed build or a route that behaves differently than you expect.
|
|
18
|
+
|
|
19
|
+
`zuplo lint` catches those problems on your machine or in CI, before you deploy.
|
|
20
|
+
It reads the same files the gateway reads and reports each problem with the
|
|
21
|
+
file, line, and column where it occurs.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx zuplo lint
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What the linter checks
|
|
28
|
+
|
|
29
|
+
The linter runs three kinds of checks against every `*.oas.json` file in your
|
|
30
|
+
project's `config/` directory and against `config/policies.json`:
|
|
31
|
+
|
|
32
|
+
- **OpenAPI quality** — every operation has an `operationId`, a description, and
|
|
33
|
+
tags; path parameters are declared; no two paths are ambiguous.
|
|
34
|
+
- **Zuplo route configuration** — every policy a route references exists in
|
|
35
|
+
`policies.json`, `x-zuplo-route` blocks contain only valid properties, and
|
|
36
|
+
routes have a label. The stricter presets also require authentication and rate
|
|
37
|
+
limiting on every route, and `zuplo:strict` requires a CORS setting.
|
|
38
|
+
- **Policy configuration** — `policies.json` has the right structure, and each
|
|
39
|
+
built-in policy's `handler.options` match that policy's schema, so a missing
|
|
40
|
+
`requestsAllowed` on a rate limit policy or a misspelled enum value shows up
|
|
41
|
+
as a warning before you deploy.
|
|
42
|
+
|
|
43
|
+
Optionally, the linter also compares your OpenAPI files against a Git branch and
|
|
44
|
+
reports [breaking changes](./api-lint-breaking-changes.mdx), such as a removed
|
|
45
|
+
endpoint.
|
|
46
|
+
|
|
47
|
+
The full list of checks, with an example of each, is in the
|
|
48
|
+
[rules reference](./api-lint-rules.mdx).
|
|
49
|
+
|
|
50
|
+
## Run the linter
|
|
51
|
+
|
|
52
|
+
Run the command from the root of your Zuplo project — the directory that
|
|
53
|
+
contains `config/`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx zuplo lint
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The linter prints each result grouped by file, followed by a summary:
|
|
60
|
+
|
|
61
|
+
```txt
|
|
62
|
+
Zuplo API Lint
|
|
63
|
+
Config: zuplo:recommended | failOn: error | source: default
|
|
64
|
+
|
|
65
|
+
config/routes.oas.json
|
|
66
|
+
6:14 warn Operations should have descriptions: `description` must be set operation-description $.paths['/todos'].get.description
|
|
67
|
+
6:14 error Every operation must have an operationId: `operationId` must be set operation-operationId $.paths['/todos'].get.operationId
|
|
68
|
+
6:14 warn Operations should have tags: `tags` must be set operation-tags $.paths['/todos'].get.tags
|
|
69
|
+
6:14 warn Route get /todos should have a label in x-zuplo-route zuplo/require-route-label paths./todos.get.x-zuplo-route.label
|
|
70
|
+
15:56 error Policy "rate-limit-inbound" is referenced in route but not defined in policies.json zuplo/no-undefined-policies paths./todos.get.x-zuplo-route.policies.inbound[1]
|
|
71
|
+
19:5 error parameter named `id` must be defined as part of the path `/todos/{id}` definition, or in the `DELETE` operation(s) path-params $.paths['/todos/{id}']
|
|
72
|
+
20:17 error Every operation must have an operationId: `operationId` must be set operation-operationId $.paths['/todos/{id}'].delete.operationId
|
|
73
|
+
20:17 warn Route delete /todos/{id} should have a label in x-zuplo-route zuplo/require-route-label paths./todos/{id}.delete.x-zuplo-route.label
|
|
74
|
+
31:36 warn Route delete /todos/{id} has an empty inbound policies array zuplo/no-empty-inbound-policies paths./todos/{id}.delete.x-zuplo-route.policies.inbound
|
|
75
|
+
|
|
76
|
+
✖ 4 errors, 5 warnings remaining
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Each line shows the `line:column` of the problem, its severity, a message, the
|
|
80
|
+
ID of the rule that reported it, and the location inside the document. Use the
|
|
81
|
+
rule ID to look the rule up in the [rules reference](./api-lint-rules.mdx) or to
|
|
82
|
+
change its severity in your [configuration](./api-lint-configuration.mdx#rules).
|
|
83
|
+
|
|
84
|
+
The header shows which presets ran, the severity that fails the run, and where
|
|
85
|
+
the configuration came from: `default` when there is no configuration file,
|
|
86
|
+
`file (auto-detected)` when the linter found a `zuplo.lint.jsonc`, or
|
|
87
|
+
`file (--config)` when you passed one.
|
|
88
|
+
|
|
89
|
+
When nothing is left to report, the run ends with `No violations found.` and
|
|
90
|
+
exits with `0`.
|
|
91
|
+
|
|
92
|
+
To lint a project in another directory, pass `--dir`:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx zuplo lint --dir ./apps/gateway
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Errors, warnings, and the exit code
|
|
99
|
+
|
|
100
|
+
Every rule reports at a severity of `error` or `warn`. By default, the command
|
|
101
|
+
fails only when it finds at least one error, so warnings point out improvements
|
|
102
|
+
without blocking you.
|
|
103
|
+
|
|
104
|
+
| Exit code | Meaning |
|
|
105
|
+
| --------- | --------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `0` | No results at or above the failure threshold. |
|
|
107
|
+
| `1` | The linter found results at or above the failure threshold. |
|
|
108
|
+
| `2` | The linter couldn't run: the directory isn't a Zuplo project, or the configuration file is invalid. |
|
|
109
|
+
|
|
110
|
+
Change the threshold with `--fail-on`:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Fail on warnings as well as errors
|
|
114
|
+
npx zuplo lint --fail-on warn
|
|
115
|
+
|
|
116
|
+
# Report everything but never fail
|
|
117
|
+
npx zuplo lint --fail-on none
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
To set the threshold for everyone on the project, set
|
|
121
|
+
[`failOn`](./api-lint-configuration.mdx#failon) in the configuration file
|
|
122
|
+
instead. The `--fail-on` flag overrides it.
|
|
123
|
+
|
|
124
|
+
## Fix problems automatically
|
|
125
|
+
|
|
126
|
+
Some problems have exactly one correct fix. Run `--fix` to apply those fixes to
|
|
127
|
+
your OpenAPI files, then lint the result:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx zuplo lint --fix
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```txt
|
|
134
|
+
Fixed
|
|
135
|
+
|
|
136
|
+
config/routes.oas.json
|
|
137
|
+
✔ Added operationId "getTodos" to GET /todos operation-operationId
|
|
138
|
+
✔ Added operationId "deleteTodosById" to DELETE /todos/{id} operation-operationId
|
|
139
|
+
✔ Removed empty inbound policies array from DELETE /todos/{id} zuplo/no-empty-inbound-policies
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The results printed after the fixes are the problems that still need your
|
|
143
|
+
attention.
|
|
144
|
+
|
|
145
|
+
`--fix` repairs two rules:
|
|
146
|
+
|
|
147
|
+
| Rule | Fix |
|
|
148
|
+
| --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
149
|
+
| `operation-operationId` | Adds an `operationId` built from the method and path. `DELETE /todos/{id}` becomes `deleteTodosById`. |
|
|
150
|
+
| `zuplo/no-empty-inbound-policies` | Removes an empty `policies.inbound` array, and the `policies` object if nothing else is left in it. |
|
|
151
|
+
|
|
152
|
+
Everything else — descriptions, tags, labels, missing policies, path parameters,
|
|
153
|
+
authentication — needs a decision only you can make, so the linter reports it
|
|
154
|
+
and leaves the file alone.
|
|
155
|
+
|
|
156
|
+
Automatic fixes are safe to run repeatedly:
|
|
157
|
+
|
|
158
|
+
- **Formatting is preserved.** The linter edits only what it fixes (plus a
|
|
159
|
+
trailing comma when it removes a property), so the change shows up as a small
|
|
160
|
+
diff rather than a reformatted file. Files with nothing to fix aren't written.
|
|
161
|
+
If an OpenAPI file isn't valid JSON, the linter changes nothing and exits with
|
|
162
|
+
`2`.
|
|
163
|
+
- **Generated `operationId` values are unique across the whole project**, not
|
|
164
|
+
only within one file. If the name is already taken, the linter adds a number:
|
|
165
|
+
`getTodos2`.
|
|
166
|
+
- **Your configuration is respected.** A rule that's off for a route, through
|
|
167
|
+
`rules`, [`overrides`](./api-lint-configuration.mdx#overrides), or
|
|
168
|
+
[`ignorePaths`](./api-lint-configuration.mdx#ignorepaths), isn't fixed on that
|
|
169
|
+
route.
|
|
170
|
+
- **Running it twice changes nothing the second time.**
|
|
171
|
+
|
|
172
|
+
:::tip
|
|
173
|
+
|
|
174
|
+
Commit or stash your changes before running `--fix`, so the fixes show up as
|
|
175
|
+
their own diff and are easy to review.
|
|
176
|
+
|
|
177
|
+
:::
|
|
178
|
+
|
|
179
|
+
## Output formats
|
|
180
|
+
|
|
181
|
+
The default `text` output is for reading in a terminal. Two other formats are
|
|
182
|
+
for tools:
|
|
183
|
+
|
|
184
|
+
| Format | Use it for |
|
|
185
|
+
| ------- | ------------------------------------------------------------------------------------------- |
|
|
186
|
+
| `text` | Reading results in a terminal. Set `NO_COLOR=1` to turn off colors. |
|
|
187
|
+
| `json` | Scripts and custom CI steps. Includes a summary, every result, breaking changes, and fixes. |
|
|
188
|
+
| `sarif` | GitHub code scanning and other tools that read SARIF 2.1.0. |
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npx zuplo lint --format json > lint-results.json
|
|
192
|
+
npx zuplo lint --format sarif > lint-results.sarif
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The `json` output has this shape:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"status": "failed",
|
|
200
|
+
"passed": false,
|
|
201
|
+
"summary": {
|
|
202
|
+
"errors": 1,
|
|
203
|
+
"warnings": 0,
|
|
204
|
+
"breakingChanges": 0,
|
|
205
|
+
"fixed": 0
|
|
206
|
+
},
|
|
207
|
+
"config": {
|
|
208
|
+
"extends": ["zuplo:recommended"],
|
|
209
|
+
"failOn": "error",
|
|
210
|
+
"source": "default"
|
|
211
|
+
},
|
|
212
|
+
"violations": [
|
|
213
|
+
{
|
|
214
|
+
"ruleId": "zuplo/no-undefined-policies",
|
|
215
|
+
"severity": "error",
|
|
216
|
+
"message": "Policy \"rate-limit-inbound\" is referenced in route but not defined in policies.json",
|
|
217
|
+
"file": "config/routes.oas.json",
|
|
218
|
+
"path": "paths./todos.get.x-zuplo-route.policies.inbound[1]",
|
|
219
|
+
"line": 15,
|
|
220
|
+
"column": 56
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
"breakingChanges": [],
|
|
224
|
+
"fixes": []
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
See [Lint in CI/CD](./api-lint-ci-cd.mdx) for workflows that use each format.
|
|
229
|
+
|
|
230
|
+
## Configure the linter
|
|
231
|
+
|
|
232
|
+
With no configuration, the linter runs the `zuplo:recommended` preset and fails
|
|
233
|
+
on errors. To choose a stricter preset, change or scope rules, add OpenAPI and
|
|
234
|
+
OWASP rulesets, write your own rules, or enable breaking change detection, add a
|
|
235
|
+
`zuplo.lint.jsonc` file to the root of your project:
|
|
236
|
+
|
|
237
|
+
```jsonc title="zuplo.lint.jsonc"
|
|
238
|
+
{
|
|
239
|
+
"$schema": "https://cdn.zuplo.com/schemas/zuplo-lint.json",
|
|
240
|
+
"version": 2,
|
|
241
|
+
"extends": ["zuplo:strict"],
|
|
242
|
+
"rules": {
|
|
243
|
+
"operation-tags": "off",
|
|
244
|
+
},
|
|
245
|
+
"overrides": [
|
|
246
|
+
{ "paths": ["/health"], "rules": { "zuplo/require-policy": "off" } },
|
|
247
|
+
],
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
See [Lint configuration](./api-lint-configuration.mdx) for every option, and run
|
|
252
|
+
`npx zuplo lint --list-rules` to see every rule you can use.
|
|
253
|
+
|
|
254
|
+
## Next steps
|
|
255
|
+
|
|
256
|
+
- [Lint configuration](./api-lint-configuration.mdx) — presets, rule severities,
|
|
257
|
+
required policies, and breaking change detection.
|
|
258
|
+
- [Lint rules reference](./api-lint-rules.mdx) — what each rule checks and how
|
|
259
|
+
to fix it.
|
|
260
|
+
- [Custom lint rules](./api-lint-custom-rules.mdx) — write your own rules and
|
|
261
|
+
share them across projects.
|
|
262
|
+
- [Breaking change detection](./api-lint-breaking-changes.mdx) — compare your
|
|
263
|
+
API with a Git branch and catch changes that break consumers.
|
|
264
|
+
- [Lint in CI/CD](./api-lint-ci-cd.mdx) — fail pull requests on lint errors and
|
|
265
|
+
show results in GitHub code scanning.
|
|
266
|
+
- [`zuplo lint` CLI reference](../cli/lint.mdx) — every flag the command
|
|
267
|
+
accepts.
|