zuplo 7.13.4 → 7.13.5

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.
@@ -0,0 +1,459 @@
1
+ ---
2
+ title: Lint Configuration
3
+ sidebar_label: Configuration
4
+ description:
5
+ Configure `zuplo lint` with zuplo.lint.jsonc to pick rulesets, scope rule
6
+ severities, override rules per route, require policies, and detect breaking
7
+ changes.
8
+ applies_to:
9
+ product: gateway
10
+ hosting: all
11
+ ---
12
+
13
+ `zuplo lint` reads its settings from a `zuplo.lint.jsonc` file in the root of
14
+ your project. The file is optional: without it, the linter runs the
15
+ `zuplo:recommended` preset and fails only on errors.
16
+
17
+ Start the file with `$schema` and `version`:
18
+
19
+ ```jsonc title="zuplo.lint.jsonc"
20
+ {
21
+ "$schema": "https://cdn.zuplo.com/schemas/zuplo-lint.json",
22
+ "version": 2,
23
+ "extends": ["zuplo:recommended"],
24
+ }
25
+ ```
26
+
27
+ `$schema` gives your editor completion and inline errors for every setting on
28
+ this page. `"version"` is the version of the configuration format; set it to
29
+ `2`.
30
+
31
+ The linter looks for `zuplo.lint.jsonc` first, then `zuplo.lint.json`. To use a
32
+ file somewhere else, pass its path with `--config`. The path is relative to the
33
+ directory you run the command from, not to `--dir`:
34
+
35
+ ```bash
36
+ npx zuplo lint --config ./lint/strict.jsonc
37
+ ```
38
+
39
+ The file is JSON with comments (JSONC), so you can annotate why a rule is off or
40
+ a path is skipped. Trailing commas are allowed.
41
+
42
+ ## Full example
43
+
44
+ ```jsonc title="zuplo.lint.jsonc"
45
+ {
46
+ "$schema": "https://cdn.zuplo.com/schemas/zuplo-lint.json",
47
+ "version": 2,
48
+
49
+ // Rulesets to start from, applied in order
50
+ "extends": [
51
+ "zuplo:strict",
52
+ ["spectral:owasp", "recommended"],
53
+ "./lint/team-ruleset.json",
54
+ ],
55
+
56
+ "rules": {
57
+ // A severity: "error", "warn", "info", "hint", or "off"
58
+ "info-contact": "off",
59
+
60
+ // A severity scoped to some routes
61
+ "operation-description": {
62
+ "severity": "error",
63
+ "paths": ["/v1/**"],
64
+ "excludePaths": ["/v1/internal/**"],
65
+ },
66
+
67
+ // A custom rule: require an API key policy on /v1 routes
68
+ "require-api-key-v1": {
69
+ "message": "v1 routes need an API key",
70
+ "severity": "error",
71
+ "paths": ["/v1/**"],
72
+ "given": "$",
73
+ "then": {
74
+ "function": "zuplo:requirePolicy",
75
+ "functionOptions": { "policyTypes": ["api-key"] },
76
+ },
77
+ },
78
+ },
79
+
80
+ // Change rules for some routes or files; later entries win
81
+ "overrides": [
82
+ {
83
+ "paths": ["/health"],
84
+ "rules": { "zuplo/require-policy": "off", "operation-tags": "off" },
85
+ },
86
+ {
87
+ "files": ["config/legacy.oas.json"],
88
+ "rules": { "operation-description": "warn" },
89
+ },
90
+ ],
91
+
92
+ // The lowest severity that fails the run
93
+ "failOn": "error",
94
+
95
+ // Compare the OpenAPI files with a Git branch
96
+ "breakingChanges": { "enabled": true, "baseline": "origin/main" },
97
+ }
98
+ ```
99
+
100
+ ## How settings combine
101
+
102
+ ```txt
103
+ extends (left to right) → rules → overrides (in order) → --fail-on flag
104
+ ```
105
+
106
+ 1. `extends` builds the starting set of rules. When two rulesets define the same
107
+ rule, the later one wins.
108
+ 2. `rules` changes the severity of those rules, limits them to some routes, or
109
+ adds new ones.
110
+ 3. `overrides` changes severities for the routes or files they match. When
111
+ several overrides match a result, the last one wins.
112
+ 4. `--fail-on` on the command line replaces [`failOn`](#failon) for one run.
113
+
114
+ ## Severities
115
+
116
+ Every rule reports at one severity:
117
+
118
+ | Severity | Meaning |
119
+ | --------- | -------------------------------------------------------------------------- |
120
+ | `"error"` | A defect. Fails the run under the default `failOn`. |
121
+ | `"warn"` | A likely problem. Fails the run only when `failOn` is `"warn"`. |
122
+ | `"info"` | Worth knowing. Reported, counted under `info` in the summary, never fails. |
123
+ | `"hint"` | A suggestion. Reported like `info`, never fails. |
124
+ | `"off"` | The rule doesn't run, or doesn't report on the routes it's off for. |
125
+
126
+ ## Options
127
+
128
+ ### `extends`
129
+
130
+ The rulesets to start from. The default is `["zuplo:recommended"]`. An entry is
131
+ one of these:
132
+
133
+ **A Zuplo preset.** Each preset combines Zuplo's own checks with a reviewed set
134
+ of OpenAPI rules that catch real defects without flooding a typical project.
135
+
136
+ | Preset | Adds |
137
+ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
138
+ | `zuplo:recommended` | The core checks: `operationId`, descriptions, tags, path parameters, undefined policies, route labels, policy schemas, duplicate operations, unsafe Markdown, and more. |
139
+ | `zuplo:strict` | Everything in recommended, plus required authentication and rate limiting, CORS on every route, API info, error responses, and documentation completeness. |
140
+ | `zuplo:security` | Everything in recommended, plus required authentication and rate limiting, both as errors. |
141
+
142
+ **A vacuum or Spectral built-in ruleset.** These bring in the full catalog of
143
+ general OpenAPI rules:
144
+
145
+ | Ruleset | Contains |
146
+ | ---------------------------------- | ------------------------- |
147
+ | `vacuum:oas` or `spectral:oas` | General OpenAPI rules. |
148
+ | `vacuum:owasp` or `spectral:owasp` | OWASP API security rules. |
149
+
150
+ On their own, these sets turn on that set's recommended rules. Write the entry
151
+ as a pair to pick a mode instead, as in Spectral:
152
+
153
+ ```jsonc
154
+ {
155
+ "version": 2,
156
+ "extends": [
157
+ "zuplo:recommended",
158
+ ["spectral:owasp", "all"], // every OWASP rule
159
+ ],
160
+ }
161
+ ```
162
+
163
+ | Mode | Effect |
164
+ | --------------- | ---------------------------------------------------- |
165
+ | `"recommended"` | The set's recommended rules. The default. |
166
+ | `"all"` | Every rule in the set. |
167
+ | `"off"` | Removes the set's rules that an earlier entry added. |
168
+
169
+ Some general OpenAPI rules don't fit Zuplo projects. For example,
170
+ `oas3-api-servers` reports every spec because Zuplo supplies the servers, and
171
+ `paths-kebab-case` reports URLPattern paths such as `/users/:id`. Turn those off
172
+ in `rules` when you extend `vacuum:oas`, or start from a Zuplo preset, which
173
+ leaves them out.
174
+
175
+ **A local ruleset file.** A `.json` or `.jsonc` file in the Spectral ruleset
176
+ format, with a path relative to `zuplo.lint.jsonc`. Use it to share rules
177
+ between projects. See
178
+ [Custom lint rules](./api-lint-custom-rules.mdx#share-rules-with-a-ruleset-file).
179
+
180
+ Run `npx zuplo lint --list-rules` to see every rule and which rulesets include
181
+ it. The [rules reference](./api-lint-rules.mdx) describes the rules in the Zuplo
182
+ presets.
183
+
184
+ ### `rules`
185
+
186
+ Rule settings, keyed by rule ID. A value is either a severity or an object.
187
+
188
+ A severity changes the rule everywhere:
189
+
190
+ ```jsonc
191
+ {
192
+ "version": 2,
193
+ "rules": {
194
+ "operation-tags": "off",
195
+ "operation-description": "error",
196
+ },
197
+ }
198
+ ```
199
+
200
+ An object changes the severity and limits which routes the rule reports on:
201
+
202
+ ```jsonc
203
+ {
204
+ "version": 2,
205
+ "rules": {
206
+ "operation-description": {
207
+ "severity": "error",
208
+ "paths": ["/v1/**"],
209
+ "excludePaths": ["/v1/internal/**"],
210
+ },
211
+ },
212
+ }
213
+ ```
214
+
215
+ | Field | Description |
216
+ | -------------- | ---------------------------------------------------------------------------- |
217
+ | `severity` | The rule's severity. Leave it out to keep the severity the rule already has. |
218
+ | `paths` | Report the rule only on these API paths. |
219
+ | `excludePaths` | Don't report the rule on these API paths. |
220
+
221
+ Path patterns work the same everywhere in this file:
222
+
223
+ - A plain path matches that route exactly, as written in the OpenAPI file:
224
+ `"/users/{userId}"`.
225
+ - A path ending in `/**` matches that path and every path beneath it.
226
+ `"/internal/**"` matches `/internal` and `/internal/jobs`, but not
227
+ `/internal2`.
228
+
229
+ A rule with `paths` doesn't report results that aren't tied to a route, such as
230
+ a missing `info.description` or a `policies.json` schema error.
231
+
232
+ An object with `given` and `then` defines a new rule. See
233
+ [Custom lint rules](./api-lint-custom-rules.mdx).
234
+
235
+ A rule ID that no ruleset in `extends` defines stops the run with exit code `2`,
236
+ and the error suggests the closest ID. To configure a rule, extend a ruleset
237
+ that includes it: `"zuplo/require-cors-policy"` is only available when you
238
+ extend `zuplo:strict`.
239
+
240
+ `zuplo/require-policy` is the rule that enforces
241
+ [`requiredPolicies`](#requiredpolicies). Setting it applies the severity to
242
+ every required policy, and `"off"` turns all of them off. Use the base ID here;
243
+ `zuplo/require-policy-warn`, which appears in results, isn't a rule you
244
+ configure.
245
+
246
+ ### `overrides`
247
+
248
+ Change rule severities for some routes or OpenAPI files. Use an override when a
249
+ group of routes is deliberately different from the rest of the API: public
250
+ endpoints, a health check, a legacy spec you can't change yet.
251
+
252
+ ```jsonc
253
+ {
254
+ "version": 2,
255
+ "overrides": [
256
+ {
257
+ "paths": ["/health", "/status"],
258
+ "rules": { "zuplo/require-policy": "off" },
259
+ },
260
+ {
261
+ "files": ["config/legacy.oas.json"],
262
+ "rules": {
263
+ "operation-description": "off",
264
+ "operation-tags": "off",
265
+ },
266
+ },
267
+ ],
268
+ }
269
+ ```
270
+
271
+ | Field | Description |
272
+ | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
273
+ | `paths` | API paths the override applies to. |
274
+ | `files` | OpenAPI files the override applies to, relative to the project directory. `*` matches within one folder: `config/*.oas.json`. |
275
+ | `rules` | Severities to apply, keyed by rule ID. |
276
+
277
+ An override needs `paths`, `files`, or both. With both, a result must match
278
+ both.
279
+
280
+ An override can also turn a rule on. With `"operation-tags": "off"` in `rules`
281
+ and `{ "paths": ["/v1/**"], "rules": { "operation-tags": "warn" } }` in
282
+ `overrides`, the rule reports only on `/v1` routes. An override works within a
283
+ rule's own `paths` and `excludePaths`, and can't turn a rule on for a path in
284
+ `ignorePaths`.
285
+
286
+ ### `failOn`
287
+
288
+ The lowest severity that fails the run with exit code `1`.
289
+
290
+ | Value | The run fails when |
291
+ | --------- | ----------------------------------------------------------------------- |
292
+ | `"error"` | There is at least one error, or a breaking change reported as an error. |
293
+ | `"warn"` | There is at least one error, warning, or breaking change. |
294
+ | `"none"` | Never. The linter reports results and exits with `0`. |
295
+
296
+ The default is `"error"`. `info` and `hint` results never fail a run. Any other
297
+ value stops the run with exit code `2`. The `--fail-on` flag overrides this
298
+ value for a single run.
299
+
300
+ ### `ignorePaths`
301
+
302
+ API paths whose route results the linter drops for every rule. An override can't
303
+ turn a rule back on for an ignored path.
304
+
305
+ ```jsonc
306
+ {
307
+ "version": 2,
308
+ "ignorePaths": ["/health", "/internal/**"],
309
+ }
310
+ ```
311
+
312
+ `ignorePaths` also applies to
313
+ [automatic fixes](./api-lint.mdx#fix-problems-automatically). Results that
314
+ aren't tied to a route still appear: `policies.json` schema results, `info`
315
+ rules, and [breaking changes](./api-lint-breaking-changes.mdx). To turn off only
316
+ some rules for a route, use [`overrides`](#overrides).
317
+
318
+ ### `include`
319
+
320
+ The OpenAPI files to lint, relative to the project directory. By default, the
321
+ linter lints every file directly in `config/` that ends in `.oas.json`; it
322
+ doesn't look in subdirectories.
323
+
324
+ ```jsonc
325
+ {
326
+ "version": 2,
327
+ "include": ["config/public-api.oas.json"],
328
+ }
329
+ ```
330
+
331
+ Every entry must end in `.oas.json`, must be a relative path, and must stay
332
+ inside the project directory. Glob patterns aren't supported, so list each file.
333
+ A file that doesn't exist fails the run with exit code `2`.
334
+
335
+ The CLI still requires at least one `.oas.json` file directly in `config/`, so
336
+ use `include` to narrow the files that are linted, not to point at files
337
+ elsewhere.
338
+
339
+ `include` controls which OpenAPI files are linted. The linter always validates
340
+ `config/policies.json` when it exists.
341
+
342
+ ### `requiredPolicies`
343
+
344
+ Policies that every route must include. The `zuplo:strict` and `zuplo:security`
345
+ presets each define a default set; `requiredPolicies` replaces those defaults
346
+ with your own list.
347
+
348
+ ```jsonc
349
+ {
350
+ "version": 2,
351
+ "extends": ["zuplo:recommended"],
352
+ "requiredPolicies": [
353
+ {
354
+ "id": "require-auth",
355
+ "policyTypes": ["api-key", "open-id-jwt-auth"],
356
+ "direction": "inbound",
357
+ "severity": "error",
358
+ "message": "Routes must have an authentication policy",
359
+ },
360
+ {
361
+ "id": "require-rate-limit",
362
+ "policyTypes": ["rate-limit"],
363
+ "severity": "warn",
364
+ "message": "Routes should have a rate limiting policy",
365
+ },
366
+ ],
367
+ }
368
+ ```
369
+
370
+ Unlike other rules, `requiredPolicies` works with any preset, including
371
+ `zuplo:recommended`. Each requirement applies to every route; to require a
372
+ policy on only some routes, use [`overrides`](#overrides) or a
373
+ [custom rule with `paths`](./api-lint-custom-rules.mdx#require-a-policy-on-some-routes).
374
+
375
+ Each entry has these fields:
376
+
377
+ | Field | Description |
378
+ | ------------- | --------------------------------------------------------------------------------------------------------- |
379
+ | `id` | A name for the requirement. Use it to keep track of entries; results report under `zuplo/require-policy`. |
380
+ | `policyTypes` | Policy types that satisfy the requirement. A route needs at least one policy whose type matches. |
381
+ | `direction` | `"inbound"` or `"outbound"`. Defaults to `"inbound"`. |
382
+ | `severity` | `"error"` or `"warn"`. Defaults to `"error"`. |
383
+ | `message` | The message to report. The linter appends the method and path of the route, for example `: get /todos`. |
384
+
385
+ The linter matches each policy name in the route's `x-zuplo-route.policies` list
386
+ to its `policyType` in `config/policies.json`. An entry in `policyTypes` matches
387
+ that exact type and any type that starts with it followed by a dash, so
388
+ `"api-key"` matches `api-key-inbound`, and `"rate-limit"` matches
389
+ `rate-limit-inbound`. Matching starts at the beginning of the type, so
390
+ `"rate-limit"` doesn't match `complex-rate-limit-inbound`; list
391
+ `"complex-rate-limit"` separately if you use it.
392
+
393
+ Requirements with `"severity": "error"` report under the rule ID
394
+ `zuplo/require-policy`. Requirements with `"severity": "warn"` report under
395
+ `zuplo/require-policy-warn`.
396
+
397
+ To turn off the preset defaults without adding your own requirements, set an
398
+ empty list:
399
+
400
+ ```jsonc
401
+ {
402
+ "version": 2,
403
+ "extends": ["zuplo:strict"],
404
+ "requiredPolicies": [],
405
+ }
406
+ ```
407
+
408
+ The defaults in `zuplo:strict` and `zuplo:security` accept these policy types:
409
+
410
+ - **Authentication:** `api-key`, `auth0-jwt-auth`, `clerk-jwt-auth`,
411
+ `cognito-jwt-auth`, `curity-phantom-token`, `firebase-jwt-auth`, `jwt-auth`,
412
+ `oauth2-pkce-auth`, `okta-jwt-auth`, `open-id-jwt-auth`, `propel-auth`,
413
+ `stytch-jwt-auth`, `supabase-jwt-auth`
414
+ - **Rate limiting:** `rate-limit`, `complex-rate-limit`
415
+
416
+ ### `breakingChanges`
417
+
418
+ Compares each OpenAPI file with the same file on a Git branch, tag, or commit,
419
+ and reports changes that can break existing API consumers, such as a removed
420
+ endpoint or a changed type.
421
+
422
+ ```jsonc
423
+ {
424
+ "version": 2,
425
+ "breakingChanges": {
426
+ "enabled": true,
427
+ "baseline": "origin/main",
428
+ "severity": "error",
429
+ },
430
+ }
431
+ ```
432
+
433
+ | Field | Description |
434
+ | ---------- | ------------------------------------------------------------------------ |
435
+ | `enabled` | Turns detection on. Defaults to `false`. |
436
+ | `baseline` | The branch, tag, or commit SHA to compare against. Defaults to `"main"`. |
437
+ | `severity` | `"error"` or `"warn"`. Defaults to `"error"`. |
438
+
439
+ See [Breaking change detection](./api-lint-breaking-changes.mdx) for how to
440
+ choose a baseline, what counts as a breaking change, and how to run it in CI.
441
+
442
+ ## Command-line flags and the configuration file
443
+
444
+ Flags take precedence over the configuration file for a single run:
445
+
446
+ | Flag | Overrides |
447
+ | ----------- | --------------------------------- |
448
+ | `--config` | Which configuration file to read. |
449
+ | `--fail-on` | [`failOn`](#failon). |
450
+
451
+ All other settings come only from the configuration file. See the
452
+ [`zuplo lint` CLI reference](../cli/lint.mdx) for every flag.
453
+
454
+ ## Next steps
455
+
456
+ - [Custom lint rules](./api-lint-custom-rules.mdx) — write your own rules and
457
+ share them with a ruleset file.
458
+ - [Lint rules reference](./api-lint-rules.mdx) — what each rule checks.
459
+ - [Lint in CI/CD](./api-lint-ci-cd.mdx) — run the linter on every pull request.
@@ -0,0 +1,222 @@
1
+ ---
2
+ title: Custom Lint Rules
3
+ sidebar_label: Custom rules
4
+ description:
5
+ Write your own `zuplo lint` rules in the Spectral format, check Zuplo routes
6
+ and policies with Zuplo functions, and share the rules with a ruleset file.
7
+ applies_to:
8
+ product: gateway
9
+ hosting: all
10
+ ---
11
+
12
+ When the built-in rules don't cover a convention your team follows, add your own
13
+ in `zuplo.lint.jsonc`. Custom rules use the
14
+ [Spectral](https://docs.stoplight.io/docs/spectral/e5b9616d6d50c-rulesets)
15
+ ruleset format, so a rule written for Spectral or vacuum works here unchanged.
16
+
17
+ ## Write a rule
18
+
19
+ A rule selects parts of your OpenAPI document with `given` and checks each one
20
+ with a function in `then`:
21
+
22
+ ```jsonc title="zuplo.lint.jsonc"
23
+ {
24
+ "$schema": "https://cdn.zuplo.com/schemas/zuplo-lint.json",
25
+ "version": 2,
26
+ "extends": ["zuplo:recommended"],
27
+ "rules": {
28
+ "operation-summary-length": {
29
+ "description": "Summaries must fit in the developer portal sidebar",
30
+ "message": "Keep summaries to 60 characters or fewer",
31
+ "severity": "warn",
32
+ "given": "$.paths[*][*].summary",
33
+ "then": { "function": "length", "functionOptions": { "max": 60 } },
34
+ },
35
+ },
36
+ }
37
+ ```
38
+
39
+ ```txt
40
+ config/routes.oas.json
41
+ 45:20 warn Keep summaries to 60 characters or fewer: 'List every todo item that belongs to the current user, sorted by the date it was created' must not be longer/greater than '60' operation-summary-length $.paths['/v1/todos'].get.summary
42
+ ```
43
+
44
+ The key, `operation-summary-length`, is the rule ID. It appears in results, and
45
+ you use it to change the rule's severity in
46
+ [`overrides`](./api-lint-configuration.mdx#overrides). IDs starting with
47
+ `zuplo/` are reserved for Zuplo's rules.
48
+
49
+ | Field | Description |
50
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `given` | A [JSONPath](https://datatracker.ietf.org/doc/html/rfc9535) expression, or a list of them, selecting the values to check. |
52
+ | `then` | The function to run on each selected value, or a list of functions. Each has `function`, and optionally `field` and `functionOptions`. |
53
+ | `severity` | `"error"`, `"warn"`, `"info"`, `"hint"`, or `"off"`. Defaults to `"warn"`. |
54
+ | `message` | The text each result starts with. Defaults to `description`, then to the rule ID. |
55
+ | `description` | What the rule checks. Used as the message when `message` isn't set. |
56
+ | `paths` | Report the rule only on these API paths, such as `["/v1/**"]`. |
57
+ | `excludePaths` | Don't report the rule on these API paths. |
58
+ | `formats` | Document formats the rule applies to, such as `["oas3"]`. |
59
+
60
+ `then.field` checks a property of each selected value instead of the value
61
+ itself, so this rule reports every operation without a `summary`:
62
+
63
+ ```jsonc
64
+ {
65
+ "version": 2,
66
+ "rules": {
67
+ "operation-summary": {
68
+ "description": "Operations need a summary",
69
+ "given": "$.paths[*][*]",
70
+ "then": { "field": "summary", "function": "truthy" },
71
+ },
72
+ },
73
+ }
74
+ ```
75
+
76
+ ## Functions
77
+
78
+ These are the general-purpose functions, the same as Spectral's core functions:
79
+
80
+ | Function | Reports when the value... | Options |
81
+ | -------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
82
+ | `truthy` | is missing, empty, `false`, or `0` | |
83
+ | `falsy` | is set to a truthy value | |
84
+ | `defined` | is missing | |
85
+ | `undefined` | is set | |
86
+ | `pattern` | doesn't match `match`, or matches `notMatch` | `match`, `notMatch` (regular expressions) |
87
+ | `casing` | isn't in the given case | `type`: `camel`, `pascal`, `kebab`, `snake`, `cobol`, `flat`, `macro` |
88
+ | `length` | is shorter than `min` or longer than `max` (characters, items, or keys) | `min`, `max` |
89
+ | `enumeration` | isn't one of `values` | `values` |
90
+ | `alphabetical` | isn't sorted | `keyedBy` for lists of objects |
91
+ | `schema` | doesn't match a JSON schema | `schema` |
92
+ | `xor` | has more or fewer than one of `properties` | `properties` |
93
+
94
+ A Zuplo route has settings, such as its policies, that the OpenAPI document
95
+ alone can't explain, because policies are defined in `config/policies.json`.
96
+ These Zuplo functions read both files:
97
+
98
+ | Function | Reports a route that... | Options |
99
+ | ------------------------------ | -------------------------------------------------------- | --------------------------------------------------------------- |
100
+ | `zuplo:requirePolicy` | doesn't have a policy of one of the given types | `policyTypes` (required), `direction` (`inbound` or `outbound`) |
101
+ | `zuplo:noUndefinedPolicies` | references a policy that isn't in `config/policies.json` | |
102
+ | `zuplo:noEmptyInboundPolicies` | has an empty `policies.inbound` list | |
103
+ | `zuplo:requireRouteLabel` | has no `label` in `x-zuplo-route` | |
104
+ | `zuplo:requireCorsPolicy` | has no `corsPolicy` in `x-zuplo-route` | |
105
+
106
+ A Zuplo function checks every route itself, so set `"given": "$"` and choose the
107
+ routes with `paths` and `excludePaths`. Only `zuplo:requirePolicy` uses your
108
+ `message`; the other Zuplo functions write their own.
109
+
110
+ Run `npx zuplo lint --list-rules` to see every function and rule available.
111
+
112
+ ## Examples
113
+
114
+ ### Require a policy on some routes
115
+
116
+ [`requiredPolicies`](./api-lint-configuration.mdx#requiredpolicies) applies to
117
+ every route. A custom rule with `zuplo:requirePolicy` applies to the routes you
118
+ choose:
119
+
120
+ ```jsonc
121
+ {
122
+ "version": 2,
123
+ "rules": {
124
+ "require-api-key-v1": {
125
+ "message": "v1 routes need an API key",
126
+ "severity": "error",
127
+ "paths": ["/v1/**"],
128
+ "given": "$",
129
+ "then": {
130
+ "function": "zuplo:requirePolicy",
131
+ "functionOptions": { "policyTypes": ["api-key"] },
132
+ },
133
+ },
134
+ },
135
+ }
136
+ ```
137
+
138
+ ```txt
139
+ 16:14 error v1 routes need an API key: get /v1/admin require-api-key-v1 paths./v1/admin.get.x-zuplo-route.policies.inbound
140
+ ```
141
+
142
+ `policyTypes` matches the same way as in `requiredPolicies`: `"api-key"` matches
143
+ the `api-key-inbound` policy type. Add one rule per requirement, each with its
144
+ own ID and `paths`.
145
+
146
+ ### Enforce a naming convention
147
+
148
+ ```jsonc
149
+ {
150
+ "version": 2,
151
+ "rules": {
152
+ "operation-id-camel-case": {
153
+ "description": "operationIds use camelCase",
154
+ "severity": "error",
155
+ "given": "$.paths[*][*].operationId",
156
+ "then": { "function": "casing", "functionOptions": { "type": "camel" } },
157
+ },
158
+ },
159
+ }
160
+ ```
161
+
162
+ ### Keep internal headers out of the public API
163
+
164
+ ```jsonc
165
+ {
166
+ "version": 2,
167
+ "rules": {
168
+ "no-x-internal-headers": {
169
+ "description": "Don't expose X-Internal-* headers",
170
+ "severity": "warn",
171
+ "given": "$.paths[*][*].parameters[?(@.in == 'header')].name",
172
+ "then": {
173
+ "function": "pattern",
174
+ "functionOptions": { "notMatch": "^X-Internal-" },
175
+ },
176
+ },
177
+ },
178
+ }
179
+ ```
180
+
181
+ ## Share rules with a ruleset file
182
+
183
+ To use the same rules in several projects, put them in a ruleset file and extend
184
+ it. A ruleset file is a `.json` or `.jsonc` file in the Spectral ruleset format:
185
+
186
+ ```jsonc title="lint/team-ruleset.jsonc"
187
+ {
188
+ // Rules shared by every API team
189
+ "rules": {
190
+ "operation-id-camel-case": {
191
+ "description": "operationIds use camelCase",
192
+ "severity": "error",
193
+ "given": "$.paths[*][*].operationId",
194
+ "then": { "function": "casing", "functionOptions": { "type": "camel" } },
195
+ },
196
+ },
197
+ }
198
+ ```
199
+
200
+ ```jsonc title="zuplo.lint.jsonc"
201
+ {
202
+ "$schema": "https://cdn.zuplo.com/schemas/zuplo-lint.json",
203
+ "version": 2,
204
+ "extends": ["zuplo:recommended", "./lint/team-ruleset.jsonc"],
205
+ }
206
+ ```
207
+
208
+ The path is relative to `zuplo.lint.jsonc`. A ruleset file can extend the
209
+ built-in rulesets (`vacuum:oas`, `spectral:oas`, `vacuum:owasp`,
210
+ `spectral:owasp`) but not other files. Rulesets must be local files: the linter
211
+ doesn't download rulesets from a URL, so copy a shared ruleset into your
212
+ repository, or add it as a package, and reference that path. YAML rulesets
213
+ aren't supported; convert them to JSON.
214
+
215
+ `zuplo.lint.jsonc` can still change the severity or scope of a rule from a
216
+ ruleset file, the same as any other rule.
217
+
218
+ ## Next steps
219
+
220
+ - [Lint configuration](./api-lint-configuration.mdx) — rulesets, severities, and
221
+ overrides.
222
+ - [Lint rules reference](./api-lint-rules.mdx) — the rules in the Zuplo presets.