diff --git a/docs/getting-started/3-rulesets.md b/docs/getting-started/3-rulesets.md index 86e195279..f6aadf051 100644 --- a/docs/getting-started/3-rulesets.md +++ b/docs/getting-started/3-rulesets.md @@ -1,161 +1,60 @@ -# Rulesets +# Create a Ruleset -Rulesets are collections of rules written in JSON, YAML, or [JavaScript](../guides/4-custom-rulesets.md#alternative-js-ruleset-format), which can be used to power powerful linting of other JSON or YAML files. Meta, we know! 😎 +Rulesets are collections of rules written in JSON, YAML, or [JavaScript](../guides/4-custom-rulesets.md#alternative-js-ruleset-format), which can be used to power powerful linting of other JSON or YAML files, such as OpenAPI or AsyncAPI descriptions. Meta, we know! 😎 -These rules are taking parameters, and calling functions on certain parts of another YAML or JSON object being linted. +Ruleset files are often named `.spectral.yaml`, but that's not a requirement. -## Anatomy of a Ruleset +Rules take certain parameters and then call functions on parts of another YAML or JSON object being linted. -A ruleset is a JSON, YAML, or JavaScript file ([often the file will be called `.spectral.yaml`](../guides/2-cli.md#using-a-ruleset-file)), and there are two main parts. +## Extend an Existing Ruleset -### Rules +The fastest way to create a ruleset is to use the `extends` property to leverage an existing ruleset. -Rules might look a bit like this: +Spectral comes with two built-in rulesets: -```yaml -rules: - paths-kebab-case: - description: Paths should be kebab-case. - message: "{{property}} should be kebab-case (lower-case and separated with hyphens)" - severity: warn - given: $.paths[*]~ - then: - function: pattern - functionOptions: - match: "^(\/|[a-z0-9-.]+|{[a-zA-Z0-9_]+})+$" -``` - -Spectral has [built-in functions](../reference/functions.md) such as `truthy` or `pattern`, which can be used to power rules. - -Rules then target certain chunks of the JSON/YAML with the `given` keyword, which is a [JSONPath](http://jsonpath.com/) (actually, we use [JSONPath Plus](https://www.npmjs.com/package/jsonpath-plus)). - -The example above adds a single rule that looks at the root level `tags` object's children to make sure they all have a `description` property. - -### JSONPath Plus - -As mentioned, spectral is using JSONPath Plus which expands on the original JSONPath specification to add some additional operators and makes explicit some behaviors the original did not spell out. - -Here are some convenient **additions or elaborations**: - -- `^` for grabbing the **parent** of a matching item -- `~` for grabbing **property names** of matching items (as array) -- **Type selectors** for obtaining: - - Basic JSON types: `@null()`, `@boolean()`, `@number()`, `@string()`, `@array()`, `@object()` - - `@integer()` - - The compound type `@scalar()` (which also accepts `undefined` and - non-finite numbers when querying JavaScript objects as well as all of the basic non-object/non-function types) - - `@other()` usable in conjunction with a user-defined `otherTypeCallback` - - Non-JSON types that can nevertheless be used when querying - non-JSON JavaScript objects (`@undefined()`, `@function()`, `@nonFinite()`) -- `@path`/`@parent`/`@property`/`@parentProperty`/`@root` **shorthand selectors** within filters -- **Escaping** - - `` ` `` for escaping remaining sequence - - `@['...']`/`?@['...']` syntax for escaping special characters within - property names in filters -- Documents `$..` (**getting all parent components**) - -### Extending Rulesets - -Rulesets can extend other rulesets using the `extends` property, allowing you to pull in other rulesets. - -```yaml -extends: spectral:oas -``` +- `spectral:oas` - [OpenAPI v2/v3 rules](./4-openapi.md) +- `spectral:asyncapi` - [AsyncAPI v2 rules](./5-asyncapi.md) -Extends can reference any [distributed ruleset](../guides/7-sharing-rulesets.md). It can be a single string, or an array of strings, and can contain either local file paths, URLs, or even npm modules. +To create a ruleset that extends both rulesets, open your terminal and run: -```yaml -extends: - - ./config/spectral.json - - https://example.org/api/style.yaml - - some-npm-module # note that this would be treated as any other npm package, therefore it has to be placed under node_modules and have a valid package.json. +```bash +echo 'extends: ["spectral:oas", "spectral:asyncapi"]' > .spectral.yaml ``` -The `extends` keyword can be combined with extra rules in order to extend and override rulesets. Learn more about that in [custom rulesets](../guides/4-custom-rulesets.md). - -### Formats - -Formats are an optional way to specify which API description formats a rule, or ruleset, is applicable to. Currently Spectral supports these formats: - -- `aas2` (AsyncAPI v2.x) -- `aas2_0` (AsyncAPI v2.0.0) -- `aas2_1` (AsyncAPI v2.1.0) -- `aas2_2` (AsyncAPI v2.2.0) -- `aas2_3` (AsyncAPI v2.3.0) -- `aas2_4` (AsyncAPI v2.4.0) -- `aas2_5` (AsyncAPI v2.5.0) -- `oas2` (OpenAPI v2.0) -- `oas3` (OpenAPI v3.x) -- `oas3_0` (OpenAPI v3.0.x) -- `oas3_1` (OpenAPI v3.1.x) -- `json-schema` (`$schema` says this is some JSON Schema draft) -- `json-schema-loose` (looks like JSON Schema, but no `$schema` found) -- `json-schema-draft4` (`$schema` says this is JSON Schema Draft 04) -- `json-schema-draft6` (`$schema` says this is JSON Schema Draft 06) -- `json-schema-draft7` (`$schema` says this is JSON Schema Draft 07) -- `json-schema-2019-09` (`$schema` says this is JSON Schema 2019-09) -- `json-schema-2020-12` (`$schema` says this is JSON Schema 2020-12) - -Specifying the format is optional, so you can completely ignore this if all the rules you are writing apply to any document you lint, or if you have specific rulesets for different formats. If you'd like to use one ruleset for multiple formats, the `formats` key is here to help. +The newly created ruleset file can then be used to lint any OpenAPI v2/v3 or AsyncAPI descriptions using the `spectral lint` command: -```yaml -rules: - oas3-api-servers: - description: "OpenAPI `servers` must be present and non-empty array." - formats: ["oas3"] - given: "$" - then: - field: servers - function: schema - functionOptions: - schema: - items: - type: object - minItems: 1 - type: array +```bash +spectral lint myapifile.yaml ``` -Specifying the format is optional, so you can completely ignore this if all the rules you are writing apply to any document you lint, or if you have specific rulesets for different formats. +## Write Your First Rule -Formats can be specified at the ruleset level: +Here's what a ruleset with a single rule might look like: ```yaml -formats: ["oas3"] rules: - oas3-api-servers: - description: "OpenAPI `servers` must be present and non-empty array." - given: "$" + paths-kebab-case: + description: Paths should be kebab-case. + message: "{{property}} should be kebab-case (lower-case and separated with hyphens)" + severity: warn + given: $.paths[*]~ then: - # ... + function: pattern + functionOptions: + match: "^(\/|[a-z0-9-.]+|{[a-zA-Z0-9_]+})+$" ``` -Now all the rules in this ruleset will only be applied if the specified format is detected. - -If you'd like to use one ruleset for multiple formats but some rules only apply to one format, you can place the `formats` keyword at the rule level instead: - -```yaml -rules: - oas3-api-servers: - description: "OpenAPI `servers` must be present and non-empty array." - formats: ["oas3"] - given: "$" - then: - # ... - oas2-hosts: - description: "OpenAPI `servers` must be present and non-empty array." - formats: ["oas2"] - given: "$" - then: - # ... -``` +The example above is a rule that can be used to validate an OpenAPI description. It will look at all the `paths` properties to make sure they are kebab-case (lower-case and separated with hyphens). -Custom formats can be registered via the [JS API](../guides/3-javascript.md), but the [CLI](../guides/2-cli.md) is limited to using the predefined formats. +Breaking down each part of the rule: -## Core Rulesets +- `description` and `message` help users quickly understand what the goal of the rule is +- `severity` help define the importance of following the rule +- The `given` keyword tells Spectral what part of the JSON or YAML file to target by using [JSONPath](http://jsonpath.com/) (Spectral uses [JSONPath Plus](https://www.npmjs.com/package/jsonpath-plus)). +- The `then` property includes the `function` type and options that tells Spectral how to apply the function to the JSON or YAML file, and make sure that the rule is being followed or not. Spectral has a set of [built-in functions](../reference/functions.md) such as `truthy` or `pattern`, which can be used to power rules. -Spectral comes with two rulesets included: +## Next Steps -- `spectral:oas` - [OpenAPI v2/v3 rules](./4-openapi.md) -- `spectral:asyncapi` - [AsyncAPI v2 rules](./5-asyncapi.md) +For more information about creating Rulesets and Rules, see [Custom Rulesets](../guides/4-custom-rulesets.md). -You can also make your own: read more about [Custom Rulesets](../guides/4-custom-rulesets.md). +For more examples of existing rulesets you can use, see [Real-World Rulesets](../../README.md#-real-world-rulesets). diff --git a/docs/guides/4-custom-rulesets.md b/docs/guides/4-custom-rulesets.md index 820f98882..a79132f34 100644 --- a/docs/guides/4-custom-rulesets.md +++ b/docs/guides/4-custom-rulesets.md @@ -1,247 +1,83 @@ -# Custom Rulesets +# Rulesets -Customizing existing rulesets might be all you need at first, but at some point, you will want to make a custom ruleset. For example, the OpenAPI and AsyncAPI rulesets help create better quality descriptions of APIs, but you could create a custom ruleset to tell you how to make better APIs. This approach is how huge companies automate [API Style Guides](https://stoplight.io/api-style-guides-guidelines-and-best-practices/?utm_source=github&utm_medium=spectral&utm_campaign=docs), instead of writing up giant Wiki documents that nobody reads. +Spectral comes with two rulesets built-in: [OpenAPI](../reference/openapi-rules.md) and [AsyncAPI](../reference/asyncapi-rules.md). They're good starting points, but the true power of Spectral comes with customizing and creating a ruleset that fits your project or organization. Creating a ruleset can help you and your team level up your API design and API development process, and help you create better APIs. -If you'd like to make sure your APIs are consistent and high quality even before they're built, create a ruleset with rules that define how URLs should work, what security schemes are appropriate, or what error formats should be used. Read our article _[Six Things You Should Include in Your API Style Guide](https://blog.stoplight.io/six-things-you-should-include-in-your-api-style-guide?utm_source=github&utm_medium=spectral&utm_campaign=docs)._ +Let's look through the various keywords that make up a ruleset, so you can learn how to tweak a distributed ruleset to work for you, or make your own ruleset from scratch to power your organizations [API Style Guide](https://stoplight.io/api-style-guides-guidelines-and-best-practices/?utm_source=github&utm_medium=spectral&utm_campaign=docs). -Or you can create a custom ruleset to make sure your Jekyll or Gatsby custom data is valid. Whatever you want to do, to start with you'll need to create some rules. +## Ruleset Properties -## Adding Rules +There are five properties that can be used at the root level of a ruleset: -Add your own rules under the `rules` property in your `.spectral.yml`, or another ruleset file. +- `rules` (required): An array of rules. See [Rules](./4a-rules.md) for more details. +- `extends` (optional): A reference to other rulesets. Used to extend and customize existing rulesets. See [Extends](./4b-extends.md) for more details. +- `formats` (optional): The format that the ruleset should apply to. For example, `oas3` for any OpenAPI v3.x descriptions. Can be applied at the ruleset and/or rule level. See [Formats](#formats) for more details. +- `documentationUrl` (optional): A URL that contains more information about the ruleset and rules in it. Can help provide users more context on why the ruleset exists and how it should be used. See [Documentation URL](#documentation-url) for more details. +- `parserOptions` (optional): Can be used to tune the severity of duplicate keys or invalid values in your ruleset. See [Parsing Options](#parsing-options) for more details. +- `aliases` (optional): An array of key-value pairs that can be used to define commonly used JSONPath expressions to be reused across a ruleset. See [Aliases](./4c-aliases.md) for more details. +- `overrides` (optional): Can be used to customize which formats, files, or parts of files, that a ruleset should be applied to. See [Overrides](./4d-overrides.md) for more details. -```yaml -rules: - my-rule-name: - description: Tags must have a description. - given: $.tags[*] - severity: error - then: - field: description - function: truthy -``` - -Spectral has [built-in functions](../reference/functions.md) such as `truthy` or `pattern`, which can be used to power rules. - -### Given - -The `given` property is conceptually quite like a selector in CSS, in that it picks the part of the document to apply rules to. - -It has a specific syntax known as [JSONPath](https://goessner.net/articles/JsonPath/index.html), which if you are familiar with XPath is quite similar. JSONPath is not yet a standard (it [will be](https://tools.ietf.org/html/draft-normington-jsonpath-00) someday), and has a few competing implementations. Spectral uses [nimma](https://www.npmjs.com/package/nimma) as its main implementation, and sometimes resorts to [jsonpath-plus](https://www.npmjs.com/package/jsonpath-plus) to ensure a good backwards-compatibility. -Both of them support all the main JSONPath functionality and a little bit more, but this syntax may differ slightly from other JSONPath implementations. - -Your `given` value can be a string containing any valid JSONPath expression, or an array of expressions to apply a rule to multiple parts of a document. -You can also consume your [aliases](#aliases) here if you have some defined. +Rules are the most important part of a ruleset. For more details on rules and its properties, see [Rules](./4a-rules.md). -Use the [JSONPath Online Evaluator](http://jsonpath.com/) to determine what `given` path you want. +### Formats -### Severity +Formats are an optional way to specify which API description formats a rule, or ruleset, is applicable to. Currently Spectral supports these formats: -The `severity` keyword is optional and can be `error`, `warn`, `info`, or `hint`. +- `aas2` (AsyncAPI v2.x) +- `aas2_0` (AsyncAPI v2.0.0) +- `aas2_1` (AsyncAPI v2.1.0) +- `aas2_2` (AsyncAPI v2.2.0) +- `aas2_3` (AsyncAPI v2.3.0) +- `aas2_4` (AsyncAPI v2.4.0) +- `aas2_5` (AsyncAPI v2.5.0) +- `oas2` (OpenAPI v2.0) +- `oas3` (OpenAPI v3.x) +- `oas3_0` (OpenAPI v3.0.x) +- `oas3_1` (OpenAPI v3.1.x) +- `json-schema` (`$schema` says this is some JSON Schema draft) +- `json-schema-loose` (looks like JSON Schema, but no `$schema` found) +- `json-schema-draft4` (`$schema` says this is JSON Schema Draft 04) +- `json-schema-draft6` (`$schema` says this is JSON Schema Draft 06) +- `json-schema-draft7` (`$schema` says this is JSON Schema Draft 07) +- `json-schema-2019-09` (`$schema` says this is JSON Schema 2019-09) +- `json-schema-2020-12` (`$schema` says this is JSON Schema 2020-12) -The default value is `warn`. +Specifying the format is optional, so you can ignore this if all the rules you are writing apply to any document you lint, or if you have specific rulesets for different formats. If you'd like to use one ruleset for multiple formats, use the `formats` key. -### Resolved - -By default, Spectral processes each rule on a "resolved" document (a file where -all `$ref` JSON Schema references have been replaced with the objects they point -to). While this is typically the desired behavior, there are some use cases -where you may need to run a rule on the "raw" un-resolved document. - -For example, if you want to enforce conventions on the folder structure used for -[splitting up -documents](https://blog.stoplight.io/keeping-openapi-dry-and-portable?utm_medium=spectral&utm_source=github&utm_campaign=docs). - -If your rule needs to access the raw `$ref` reference values, you can set `resolved: false` to allow the rule to receive the raw un-resolved version of the document. Otherwise `resolved: true` is the default. - -Here's an example of a rule that can access `$ref` values: +Formats can be specified at the ruleset level: ```yaml +formats: ["oas3"] rules: - my-rule-name: - description: Parameters must be references - given: $.paths.[*][get,post,put,delete,options] - severity: error - resolved: false + oas3-api-servers: + description: "OpenAPI `servers` must be present and non-empty array." + given: "$" then: - field: parameters - function: schema - functionOptions: - schema: - type: array - items: - type: object - properties: - $ref: - type: string - required: - - $ref -``` - -**In most cases, you will want to operate on a resolved document.** - -### Then - -The `then` part of the rule explains which function to apply to the `given` JSONPath. The function you apply [may be one of the core functions](../reference/functions.md) or it may be [a custom function](./5-custom-functions.md). - -`then` has two main keywords: - -```yaml -then: - field: description - function: truthy + # ... ``` -The `field` keyword is optional and is used to apply the function to a specific property in an object. If omitted the function will be applied to the entire target of the `given` JSONPath. The value can also be `@key` to apply the rule to the keys of an object. +Now all the rules in this ruleset will only be applied if the specified format is detected. -```yaml -given: "$.responses" -then: - field: "@key" - function: pattern - functionOptions: - match: "^[0-9]+$" -``` - -The above [`pattern` based rule](../reference/functions.md#pattern) would error on `456avbas` as it is not numeric. - -```yaml -responses: - 123: - foo: bar - 456avbas: - foo: bar -``` - -You can also have multiple `then`s to target different properties in the same object, or to use different functions. For example, you can have one rule that will check if an object has multiple properties: - -```yaml -contact-properties: - description: Contact object must have "name", "url", and "email". - given: $.info.contact - severity: warn - then: - - field: name - function: truthy - - field: url - function: truthy - - field: email - function: truthy -``` - -### Message - -To help you create meaningful messages for results, Spectral comes with a couple of placeholders that are evaluated at runtime. - -- `{{error}}` - the error returned by function -- `{{description}}` - the description set on the rule -- `{{path}}` - the whole error path -- `{{property}}` - the last segment of error path -- `{{value}}` - the linted value +If you'd like to use one ruleset for multiple formats but some rules only apply to one format, you can place the `formats` keyword at the rule level instead: ```yaml -message: "{{error}}" # will output the message generated by then.function -``` - -```yaml -message: "The value of '{{property}}' property must equal 'foo'" -``` - -```yaml -message: "{{value}} is greater than 0" -``` - -```yaml -message: "{{path}} cannot point at remote reference" -``` - -## Modifying Rules - -When extending another ruleset, you can replace a rule defined in that ruleset by adding a new rule to your own ruleset with the same name. - -```yaml -extends: spectral:oas rules: - tag-description: - description: Please provide a description for each tag. - given: $.tags[*] + oas3-api-servers: + description: "OpenAPI `servers` must be present and non-empty array." + formats: ["oas3"] + given: "$" then: - field: description - function: truthy -``` - -This provides a new description, but anything can be changed. - -If you're just looking to change the severity of the rule, there is a handy shortcut. - -### Changing Rule Severity - -Maybe you want to use the rules from the `spectral:oas` ruleset, but instead of `operation-success-response` triggering an error you'd like it to trigger a warning instead. - -```yaml -extends: spectral:oas -rules: - operation-success-response: warn -``` - -Available severity levels are `error`, `warn`, `info`, `hint`, and `off`. - -## Recommended or All - -Rules by default are considered "recommended" (equivalent to a rule having) `recommended: true` but they can also be marked as not recommended with `recommended: false`. This can help scenarios like rolling out rulesets across API landscapes with a lot of legacy APIs which might have a hard time following every rule immediately. A two-tier system for rules can be helpful here, to avoid requiring several rulesets for this basic use case. - -You can try this out with the core OpenAPI ruleset. If you simply extend the ruleset, by default you will only get the recommended rules. - -```yaml -extends: [[spectral:oas, recommended]] -``` - -Far more rules exist than just the recommended ones, there are various other rules which will help you create high-quality OpenAPI descriptions. - -```yaml -extends: [[spectral:oas, all]] -``` - -You can do this with your rulesets, and slide new rules in as not recommended for a while so that only the most interested active API designers/developers get them at first, then eventually roll them out to everyone if they are well received. - -### Disabling Rules - -This example shows the opposite of the "Enabling Specific Rules" example. Sometimes you might want to enable all rules by default, and disable a few. - -```yaml -extends: [[spectral:oas, all]] -rules: - operation-operationId-unique: off -``` - -The example above will run all of the rules defined in the `spectral:oas` ruleset (rather than the default behavior that runs only the recommended ones), with one exception - we turned `operation-operationId-unique` off. - -### Enabling Rules - -Sometimes you might want to apply a limited number of rules from another ruleset. To do this, use the `extends` property with `off` as the second argument. This will avoid running any rules from the extended ruleset as they will all be disabled. Then you can pick and choose which rules you would like to enable. - -```yaml -extends: [[spectral:oas, off]] -rules: - operation-operationId-unique: true -``` - -The example above will only run the rule `operation-operationId-unique` that we enabled since we passed `off` to disable all rules by default when extending the `spectral:oas` ruleset. - -## Parsing Options - -If you do not care about duplicate keys or invalid values (such as non-string mapping keys in YAML), you can tune their severity using the `parserOptions` setting. - -```yaml -extends: spectral:oas -parserOptions: - duplicateKeys: warn # error is the default value - incompatibleValues: off # error is the default value + # ... + oas2-hosts: + description: "OpenAPI `servers` must be present and non-empty array." + formats: ["oas2"] + given: "$" + then: + # ... ``` -`parserOptions` is not inherited by extended rulesets. +Custom formats can be registered via the [JS API](../guides/3-javascript.md), but the [CLI](../guides/2-cli.md) is limited to using the predefined formats. -## Documentation URL +### Documentation URL Optionally provide a documentation URL to your ruleset in order to help end-users find more information about various warnings. Result messages will sometimes be more than enough to explain what the problem is, but it can also be beneficial to explain _why_ a message exists, and this is a great place to do that. @@ -281,361 +117,17 @@ rules: function: truthy ``` -## Core Functions - -Several functions [are provided by default](../reference/functions.md) for your rules. - -## Custom Functions - -If none of the [core functions](../reference/functions.md) do what you want, you can [write your own custom functions](./5-custom-functions.md). - -## Aliases - -Targeting certain parts of an OpenAPI spec is powerful but it can become cumbersome to write and repeat complex JSONPath expressions across various rules. -Define aliases for commonly used JSONPath expressions on a global level which can then be reused across the ruleset. - -Aliases can be defined in an array of key-value pairs at the root level of the ruleset, or alternatively, within an override. -It's similar to `given`, with the notable difference being the possibility to distinguish between different formats. - -**Example** - -```yaml -aliases: - HeaderNames: - - "$..parameters.[?(@.in === 'header')].name" - Info: - - "$..info" - InfoDescription: - - "#Info.description" - InfoContact: - - "#Info.contact" - Paths: - - "$.paths[*]~" -``` - -If you deal with a variety of different specs, you may find the above approach insufficient, particularly when the shape of the document is notably different. -In such a case, you may want to consider using scoped aliases. - -```yaml -aliases: - SharedParameterObject: - description: an optional property describing the purpose of the alias - targets: - - formats: - - oas2 - given: - - $.parameters[*] - - formats: - - oas3 - given: - - $.components.parameters[*] -``` - -Now, if you referenced the `SharedParameterObject` alias, the chosen path would be determined based on the document you use. -For instance, if a given document matched OpenAPI 2.x, `$.parameters[*]` would be used as the JSONPath expression. - -Having a closer look at the example above, one may notice that it'd be still somewhat complicated to target _all_ Parameter Objects that a specific OpenAPI document may contain. To make it more feasible and avoid overly complex JSONPath expressions, `given` can be an array. - -```yaml -aliases: - PathItemObject: - - $.paths[*] - OperationObject: - - "#PathItem[get,put,post,delete,options,head,patch,trace]" - ParameterObject: - description: an optional property describing the purpose of the alias - targets: - - formats: - - oas2 - given: - - "#PathItemObject.parameters[*]" - - "#OperationObject.parameters[*]" - - $.parameters[*] - - formats: - - oas3 - given: - - "#PathItemObject.parameters[*]" - - "#OperationObject.parameters[*]" - - $.components.parameters[*] -``` - -Rulesets can then reference aliases in the [given](#given) keyword, either in full: `"given": "#Paths"`, or use it as a prefix for further JSONPath syntax, like dot notation: `"given": "#ParameterObject.name"`. - -Keep in mind that an alias has to be explicitly defined either at the root level or inside an override. This is to avoid ambiguity. - -```yaml -aliases: - Stoplight: - - "$..stoplight" -overrides: - - files: - - "*.yaml" - rules: - value-matches-stoplight: - message: Value must contain Stoplight - given: "#Stoplight" # valid because declared at the root - severity: error - then: - field: description - function: pattern - functionOptions: - match: Stoplight - - files: - - "**/*.json" - aliases: - Value: - - "$..value" - rules: - truthy-stoplight-property: - message: Value must contain Stoplight - given: "#Value" # valid because declared within the override block - severity: error - then: - function: truthy - - files: - - legacy/**/*.json - rules: - falsy-value: - given: "#Value" # invalid because undeclared both at the top-level and the override. Note that this could be technically resolvable for some JSON documents because the previous override block has the alias, but to spare some headaches, we demand an alias to be explicitly defined. - severity: error - then: - function: falsy -``` - -> This will be followed by our core rulesets providing a common set of aliases for OpenAPI and AsyncAPI so that our users don't have to do the work at all. If you have ideas about what kind of aliases could be useful leave your thoughts [here](https://roadmap.stoplight.io). - -## Overrides - -Previously Spectral supported exceptions, which were limited in their ability to target particular rules on specific files or parts of files, or change parts of a rule. Overrides is the much more powerful version of exceptions, with the ability to customize ruleset usage for different files and projects without having to duplicate any rules. - -Overrides can be used to apply rulesets on: - -- Particular formats `formats: [jsonSchemaDraft7]` -- Particular files/folders `files: ['schemas/**/*.draft7.json']` -- Particular elements of files `files: ['**#/components/schemas/Item']` -- Override particular rules - -**Example** - -```yaml -overrides: - formats: - - json-schema-draft7 - files: - - schemas/**/*.draft7.json - rules: - valid-number-validation: - given: - - $..exclusiveMinimum - - $..exclusiveMaximum - then: - function: schema - functionOptions: - type: number -``` - -To apply an override to particular elements of files, combine a glob for a filepath -with a [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) after the anchor, i.e.: - -```yaml -overrides: - - files: - - "legacy/**/*.oas.json#/paths" - rules: - some-inherited-rule: "off" -``` - -JSON Pointers have a different syntax than JSON Paths used in the `given` component of a rule. -In JSON Pointers, path components are prefixed with a "/" and then concatenated to form the pointer. -Since "/" has a special meaning in JSON pointer, it must be encoded as "~1" when it appears in a component, and "~" must be encoded as "~0". - -You can test JSON Pointer expressions in the [JSON Query online evaluator](https://www.jsonquerytool.com/) by choosing "JSONPointer" as the Transform. - -```yaml -overrides: - - files: - - "legacy/**/*.oas.json#/paths/~1Pets~1{petId}/get/parameters/0" - rules: - some-inherited-rule: "off" -``` - -In the event of multiple matches, the order of definition takes place, with the last one having the higher priority. - -### Caveats - -Please bear in mind that overrides are only applied to the _root_ documents. If your documents have any external dependencies, i.e. $refs, the overrides won't apply. - -**Example:** +### Parsing Options -Given the following 2 YAML documents: + -```yaml -# my-document.yaml -openapi: "3.1.0" -paths: {} -components: - schemas: - User: - $ref: "./User.yaml" -``` +If you don't care about duplicate keys or invalid values (such as non-string mapping keys in YAML), you can tune their severity using the `parserOptions` setting. ```yaml -# User.yaml -title: "" -type: object -properties: - id: - type: string -required: - - id -``` - -And the ruleset below: - -```json -{ - "rules": { - "empty-title-property": { - "message": "Title must not be empty", - "given": "$..title", - "then": { - "function": "truthy" - } - } - }, - "overrides": [ - { - "files": ["User.yaml"], - "rules": { - "empty-title-property": "off" - } - } - ] -} -``` - -Running `spectral lint my-document.yaml` will result in the following output: - -``` -/project/User.yaml - 1:8 warning empty-title-property Title must not be empty title - -✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints) -``` - -While executing `spectral lint User.yaml` will output: - -``` -No results with a severity of 'error' or higher found! -``` - -## Alternative JS Ruleset Format - -Spectral v6.0 added support for a JavaScript ruleset format, similar to the JSON and YAML formats. - -This has a few benefits: it lets you explicitly load formats or rulesets to get control over versioning, you can load common functions from popular JS libraries, and in general feels a lot more welcoming to developers experienced with JavaScript, especially when it comes to working with custom functions. - -**Example** - -To create a JavaScript ruleset, the first step is creating a folder. In your terminal, run the following commands: - -``` -mkdir style-guide -cd style-guide -``` - -Next, install two dependencies using [npm](https://www.npmjs.com/): - -``` -npm install --save @stoplight/spectral-functions -npm install --save @stoplight/spectral-formats -``` - -Installing these packages is not required for creating a JavaScript ruleset, but we'll use them in our example to create some common rules used with Spectral and to target a specific OpenAPI format. - -Next, let's create a JavaScript file to hold our ruleset: - -``` -touch spectral.js -``` - -And inside the file, let's create a couple rules: - -```js -import { truthy, undefined as pattern, schema } from "@stoplight/spectral-functions"; -import { oas3 } from "@stoplight/spectral-formats"; - -export default { - rules: { - "api-home-get": { - description: "APIs root path (`/`) MUST have a GET operation.", - message: "Otherwise people won't know how to get it.", - given: "$.paths[/]", - then: { - field: "get", - function: truthy, - }, - severity: "warn", - }, - - // Author: Phil Sturgeon (https://github.com/philsturgeon) - "no-numeric-ids": { - description: "Avoid exposing IDs as an integer, UUIDs are preferred.", - given: '$.paths..parameters[*].[?(@property === "name" && (@ === "id" || @.match(/(_id|Id)$/)))]^.schema', - then: { - function: schema, - functionOptions: { - schema: { - type: "object", - not: { - properties: { - type: { - const: "integer", - }, - }, - }, - properties: { - format: { - const: "uuid", - }, - }, - }, - }, - }, - severity: "error", - }, - - // Author: Nauman Ali (https://github.com/naumanali-stoplight) - "no-global-versioning": { - description: "Using global versions just forces all your clients to do a lot more work for each upgrade. Please consider using API Evolution instead.", - message: "Server URL should not contain global versions", - given: "$.servers[*].url", - then: { - function: pattern, - functionOptions: { - notMatch: "/v[1-9]", - }, - }, - formats: [oas3], - severity: "warn", - }, - }, -}; -``` - -For those of you using custom functions, the keywords `functions` & `functionOptions` have been removed, as they were designed to help Spectral find your functions. Now functions are passed as a variable, instead of using a string that contains the name like the JSON/YAML formats. - -This code example should look fairly familiar for anyone who has used the JSON or YAML formats. The next steps for using this ruleset would be publishing it as an npm package, and then installing that package as part of your API project and referencing in your Spectral ruleset as: - -``` -extends: ["@your-js-ruleset"] -``` - -Or using unpkg: - -``` -extends: - - https://unpkg.com/@your-js-ruleset +extends: spectral:oas +parserOptions: + duplicateKeys: warn # error is the default value + incompatibleValues: off # error is the default value ``` -For a more detailed example of creating a JavaScript ruleset and publishing it to npm, check out [Distribute Spectral Style Guides with npm](https://apisyouwonthate.com/blog/distribute-spectral-style-guides-with-npm) at APIs You Won't Hate. +`parserOptions` is not inherited by extended rulesets. diff --git a/docs/guides/4a-rules.md b/docs/guides/4a-rules.md new file mode 100644 index 000000000..42d7bab31 --- /dev/null +++ b/docs/guides/4a-rules.md @@ -0,0 +1,158 @@ +# Rules + +Rules are at the core of how rulesets work, so let's look at how to create a rule and its properties. + +## Rules Properties + +Rules can be added under the `rules` property in your ruleset file. + +```yaml +rules: + my-rule-name: + description: Tags must have a description. + given: $.tags[*] + severity: error + then: + field: description + function: truthy +``` + +The example above is a valid ruleset with a single rule that can be used to lint an OpenAPI description, and validate that all `tags` have a description field. + +Rules can have the following properties: + +- `given` (required): The part of the document the rule should be applied to. Uses the [JSONPath](https://goessner.net/articles/JsonPath/index.html) syntax. +- `then` (required): Describes which function should be applied to the `given` part of the document. Can be used with a [core function](../reference/functions.md) or [custom function](./5-custom-functions.md). +- `description` (optional): A short description of the rule. +- `message` (optional): A message that's displayed in the `spectral lint` output. Can be customized to use placeholder values that are evaluated at runtime, such as `{{description}}` or `{{error}}`. +- `severity` (optional): The severity of the rule. Used to differentiate between rules that must be followed (`error`) and warnings or hints. Default value is `warn`. +- `formats` (optional): The format that the rule should apply to. For example `oas3` for any OpenAPI v3.x descriptions. Can be applied at the ruleset and/or rule level. See [Formats](./4-custom-rulesets.md#formats) for more details. +- `recommended` (optional): Recommended is a property that is used when extending a ruleset, where users can define if they would like to enforce all rules (`recommended` set to `true` and `false`) or only recommended rules (`recommended` set to `true`). Recommended can be used to help slowly roll out a ruleset across API landscapes with a lot of legacy APIs. Default value is `true`. See [Recommended](./4e-recommended.md) for more details. +- `resolved` (optional): Used to apply a rule to a document that is not "resolved", where `$ref` JSON Schema references have not been replaced with the objects they point to. + +Let's look at all the properties that can be used for a rule. + +### Given + +The `given` property is conceptually quite like a selector in CSS, in that it indicates the part of the document to apply rules to. + +It has a specific syntax known as [JSONPath](https://goessner.net/articles/JsonPath/index.html), which if you are familiar with XPath is quite similar. JSONPath is not yet a standard (it [will be](https://tools.ietf.org/html/draft-normington-jsonpath-00) someday), and has a few competing implementations. Spectral uses [nimma](https://www.npmjs.com/package/nimma) as its main implementation, and sometimes resorts to [jsonpath-plus](https://www.npmjs.com/package/jsonpath-plus) to ensure good backwards-compatibility. +Both of them support all the main JSONPath functionality and a little bit more, but this syntax may differ slightly from other JSONPath implementations. + +Your `given` value can be a string containing any valid JSONPath expression, or an array of expressions to apply a rule to multiple parts of a document. +You can also consume your [aliases](4c-aliases.md) here if you have some defined. + +Use the [JSONPath Online Evaluator](http://jsonpath.com/) to determine what `given` path you want. + +### Then + +The `then` part of the rule explains which function to apply to the `given` JSONPath. The function you apply [may be one of the core functions](../reference/functions.md) or it may be [a custom function](./5-custom-functions.md). + +`then` has two main keywords: + +```yaml +then: + field: description + function: truthy +``` + +The `field` keyword is optional and is used to apply the function to a specific property in an object. If omitted, the function will be applied to the entire target of the `given` JSONPath. The value can also be `@key` to apply the rule to the keys of an object. + +```yaml +given: "$.responses" +then: + field: "@key" + function: pattern + functionOptions: + match: "^[0-9]+$" +``` + +The above [`pattern` based rule](../reference/functions.md#pattern) would error on `456avbas` as it is not numeric. + +```yaml +responses: + 123: + foo: bar + 456avbas: + foo: bar +``` + +You can also have multiple `then`s to target different properties in the same object, or to use different functions. For example, you can have one rule that will check if an object has multiple properties: + +```yaml +contact-properties: + description: Contact object must have "name", "url", and "email". + given: $.info.contact + severity: warn + then: + - field: name + function: truthy + - field: url + function: truthy + - field: email + function: truthy +``` + +### Message + +To help you create meaningful messages for results, Spectral comes with placeholders that are evaluated at runtime. + +- `{{error}}` - the error returned by function +- `{{description}}` - the description set on the rule +- `{{path}}` - the whole error path +- `{{property}}` - the last segment of error path +- `{{value}}` - the linted value + +```yaml +message: "{{error}}" # will output the message generated by then.function +``` + +```yaml +message: "The value of '{{property}}' property must equal 'foo'" +``` + +```yaml +message: "{{value}} is greater than 0" +``` + +```yaml +message: "{{path}} cannot point at remote reference" +``` + +### Severity + +The `severity` keyword is optional and can be `error`, `warn`, `info`, `hint`, or `off`. + +The default value is `warn`. + +### Resolved + +By default, Spectral processes each rule on a "resolved" document (a file where all `$ref` JSON Schema references have been replaced with the objects they point to). While this is typically the desired behavior, there are some use cases where you may need to run a rule on the "raw" un-resolved document (for example, if you want to enforce conventions on the folder structure used for [splitting up documents](https://blog.stoplight.io/keeping-openapi-dry-and-portable?utm_medium=spectral&utm_source=github&utm_campaign=docs)). + +If your rule needs to access the raw `$ref` reference values, you can set `resolved: false` to allow the rule to receive the raw un-resolved version of the document. Otherwise `resolved: true` is the default. + +Here's an example of a rule that can access `$ref` values: + +```yaml +rules: + my-rule-name: + description: Parameters must be references + given: $.paths[*][get,post,put,delete,options] + severity: error + resolved: false + then: + field: parameters + function: schema + functionOptions: + schema: + type: array + items: + type: object + properties: + $ref: + type: string + required: + - $ref +``` + +**In most cases, you will want to operate on a resolved document.** diff --git a/docs/guides/4b-extends.md b/docs/guides/4b-extends.md new file mode 100644 index 000000000..4e63112d0 --- /dev/null +++ b/docs/guides/4b-extends.md @@ -0,0 +1,49 @@ +# Extending Rulesets + +Rulesets can extend other rulesets using the `extends` property, allowing you to pull in other rulesets. + +```yaml +extends: spectral:oas +``` + +Extends can reference any [distributed ruleset](../guides/7-sharing-rulesets.md). It can be a single string, or an array of strings, and can contain either local file paths, URLs, or even npm modules. + +```yaml +extends: + - ./config/spectral.json + - https://example.org/api/style.yaml + - some-npm-module # note that this would be treated as any other npm package, therefore it has to be placed under node_modules and have a valid package.json. +``` + +The `extends` keyword can be combined with extra rules to extend and override rulesets. Learn more about that in [custom rulesets](../guides/4-custom-rulesets.md). + +## Modify Rules + +When extending another ruleset, you can replace a rule defined in that ruleset by adding a new rule to your own ruleset with the same name. + +```yaml +extends: spectral:oas +rules: + tag-description: + description: Please provide a description for each tag. + given: $.tags[*] + then: + field: description + function: truthy +``` + +This provides a new description, but anything can be changed. + +If you're just looking to change the severity of the rule, there's a handy shortcut. + +## Change Rule Severity + +Maybe you want to use the rules from the `spectral:oas` ruleset, but instead of `operation-success-response` triggering an error you'd like it to trigger a warning instead. + +```yaml +extends: spectral:oas +rules: + operation-success-response: warn +``` + +See [Severity](./4a-rules.md#severity) for more details. diff --git a/docs/guides/4c-aliases.md b/docs/guides/4c-aliases.md new file mode 100644 index 000000000..0a4e11627 --- /dev/null +++ b/docs/guides/4c-aliases.md @@ -0,0 +1,117 @@ +## Aliases + +Targeting certain parts of an OpenAPI spec is powerful but it can become cumbersome to write and repeat complex JSONPath expressions across various rules. + +Define aliases for commonly used JSONPath expressions on a global level and then reuse them across the ruleset. + +Aliases can be defined in an array of key-value pairs at the root level of the ruleset, or alternatively, within an override. + +It's similar to `given`, with the notable difference being the possibility to distinguish between different formats. + +**Example** + +```yaml +aliases: + HeaderNames: + - "$..parameters.[?(@.in === 'header')].name" + Info: + - "$..info" + InfoDescription: + - "#Info.description" + InfoContact: + - "#Info.contact" + Paths: + - "$.paths[*]~" +``` + +If you deal with a variety of different specs, you may find the above approach insufficient, particularly when the shape of the document is notably different. +In such a case, you may want to consider using scoped aliases. + +```yaml +aliases: + SharedParameterObject: + description: an optional property describing the purpose of the alias + targets: + - formats: + - oas2 + given: + - $.parameters[*] + - formats: + - oas3 + given: + - $.components.parameters[*] +``` + +Now, if you referenced the `SharedParameterObject` alias, the chosen path would be determined based on the document you use. + +For instance, if a given document matched OpenAPI 2.x, `$.parameters[*]` would be used as the JSONPath expression. + +Having a closer look at the example above, one may notice that it'd be still somewhat complicated to target _all_ Parameter Objects that a specific OpenAPI document may contain. To make it more feasible and avoid overly complex JSONPath expressions, `given` can be an array. + +```yaml +aliases: + PathItemObject: + - $.paths[*] + OperationObject: + - "#PathItem[get,put,post,delete,options,head,patch,trace]" + ParameterObject: + description: an optional property describing the purpose of the alias + targets: + - formats: + - oas2 + given: + - "#PathItemObject.parameters[*]" + - "#OperationObject.parameters[*]" + - $.parameters[*] + - formats: + - oas3 + given: + - "#PathItemObject.parameters[*]" + - "#OperationObject.parameters[*]" + - $.components.parameters[*] +``` + +Rulesets can then reference aliases in the [given](./4a-rules.md#given) keyword, either in full: `"given": "#Paths"`, or use it as a prefix for further JSONPath syntax, like dot notation: `"given": "#ParameterObject.name"`. + +Keep in mind that an alias has to be explicitly defined either at the root level or inside an override. This is to avoid ambiguity. + +```yaml +aliases: + Stoplight: + - "$..stoplight" +overrides: + - files: + - "*.yaml" + rules: + value-matches-stoplight: + message: Value must contain Stoplight + given: "#Stoplight" # valid because declared at the root + severity: error + then: + field: description + function: pattern + functionOptions: + match: Stoplight + - files: + - "**/*.json" + aliases: + Value: + - "$..value" + rules: + truthy-stoplight-property: + message: Value must contain Stoplight + given: "#Value" # valid because declared within the override block + severity: error + then: + function: truthy + - files: + - legacy/**/*.json + rules: + falsy-value: + given: "#Value" # invalid because undeclared both at the top-level and the override. Note that this could be technically resolvable for some JSON documents because the previous override block has the alias, but to spare some headaches, we demand an alias to be explicitly defined. + severity: error + then: + function: falsy +``` + +> This will be followed by our core rulesets providing a common set of aliases for OpenAPI and AsyncAPI so that our users don't have to do the work at all. If you have ideas about what kind of aliases could be useful leave your thoughts [here](https://roadmap.stoplight.io). diff --git a/docs/guides/4d-overrides.md b/docs/guides/4d-overrides.md new file mode 100644 index 000000000..fd11f140b --- /dev/null +++ b/docs/guides/4d-overrides.md @@ -0,0 +1,125 @@ +## Overrides + +Previously Spectral supported exceptions, which were limited in their ability to target particular rules on specific files or parts of files, or change parts of a rule. Overrides are the much more powerful version of exceptions, with the ability to customize ruleset usage for different files and projects without having to duplicate any rules. + +Overrides can be used to apply rulesets on: + +- Particular formats `formats: [jsonSchemaDraft7]` +- Particular files/folders `files: ['schemas/**/*.draft7.json']` +- Particular elements of files `files: ['**#/components/schemas/Item']` +- Override particular rules + +**Example** + +```yaml +overrides: + formats: + - json-schema-draft7 + files: + - schemas/**/*.draft7.json + rules: + valid-number-validation: + given: + - $..exclusiveMinimum + - $..exclusiveMaximum + then: + function: schema + functionOptions: + type: number +``` + +To apply an override to particular elements of files, combine a glob for a filepath with a [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) after the anchor, i.e.: + +```yaml +overrides: + - files: + - "legacy/**/*.oas.json#/paths" + rules: + some-inherited-rule: "off" +``` + +JSON Pointers have a different syntax than JSON Paths used in the `given` component of a rule. + +In JSON Pointers, path components are prefixed with a "/" and then concatenated to form the pointer. + +Since "/" has a special meaning in JSON pointer, it must be encoded as "~1" when it appears in a component, and "~" must be encoded as "~0". + +You can test JSON Pointer expressions in the [JSON Query online evaluator](https://www.jsonquerytool.com/) by choosing "JSONPointer" as the Transform. + +```yaml +overrides: + - files: + - "legacy/**/*.oas.json#/paths/~1Pets~1{petId}/get/parameters/0" + rules: + some-inherited-rule: "off" +``` + +In the event of multiple matches, the order of definition takes place, with the last one having the higher priority. + +### Caveats + +Overrides are only applied to the _root_ documents. If your documents have any external dependencies, i.e. $refs, the overrides won't apply. + +**Example:** + +Given the following 2 YAML documents: + +```yaml +# my-document.yaml +openapi: "3.1.0" +paths: {} +components: + schemas: + User: + $ref: "./User.yaml" +``` + +```yaml +# User.yaml +title: "" +type: object +properties: + id: + type: string +required: + - id +``` + +And the ruleset below: + +```json +{ + "rules": { + "empty-title-property": { + "message": "Title must not be empty", + "given": "$..title", + "then": { + "function": "truthy" + } + } + }, + "overrides": [ + { + "files": ["User.yaml"], + "rules": { + "empty-title-property": "off" + } + } + ] +} +``` + +Running `spectral lint my-document.yaml` will result in the following output: + +``` +/project/User.yaml + 1:8 warning empty-title-property Title must not be empty title + +✖ 1 problem (0 errors, 1 warning, 0 infos, 0 hints) +``` + +While executing `spectral lint User.yaml` will output: + +``` +No results with a severity of 'error' or higher found! +``` diff --git a/docs/guides/4e-recommended.md b/docs/guides/4e-recommended.md new file mode 100644 index 000000000..f19cc6d6f --- /dev/null +++ b/docs/guides/4e-recommended.md @@ -0,0 +1,41 @@ +## Recommended or All + +Rules by default are considered "recommended" (equivalent to a rule having) `recommended: true` but they can also be marked as not recommended with `recommended: false`. This can help scenarios like rolling out rulesets across API landscapes with a lot of legacy APIs which might have a hard time following every rule immediately. A two-tier system for rules can be helpful here, to avoid requiring several rulesets for this basic use case. + +You can try this out with the core OpenAPI ruleset. If you simply extend the ruleset, by default you will only get the recommended rules. + +```yaml +extends: [[spectral:oas, recommended]] +``` + +Far more rules exist than just the recommended ones, there are various other rules which will help you create high-quality OpenAPI descriptions. + +```yaml +extends: [[spectral:oas, all]] +``` + +You can do this with your rulesets, and initially set new rules as "not recommended" so that the most interested active API designers/developers use them at first. Then, eventually roll them out to everyone if they are well received. + +### Disabling Rules + +This example shows the opposite of the "Enabling Specific Rules" example. Sometimes you might want to enable all rules by default, and disable a few. + +```yaml +extends: [[spectral:oas, all]] +rules: + operation-operationId-unique: off +``` + +The example above runs all of the rules defined in the `spectral:oas` ruleset (rather than the default behavior that runs only the recommended ones), with one exception; `operation-operationId-unique` is set to off. + +### Enabling Rules + +Sometimes you might want to apply a limited number of rules from another ruleset. To do this, use the `extends` property with `off` as the second argument. This avoids running any rules from the extended ruleset as they will all be disabled. Then you can pick and choose which rules you would like to enable. + +```yaml +extends: [[spectral:oas, off]] +rules: + operation-operationId-unique: true +``` + +The example above only runs the rule `operation-operationId-unique` that we enabled since we passed `off` to disable all rules by default when extending the `spectral:oas` ruleset. diff --git a/docs/guides/4f-js-rulesets.md b/docs/guides/4f-js-rulesets.md new file mode 100644 index 000000000..e07293a10 --- /dev/null +++ b/docs/guides/4f-js-rulesets.md @@ -0,0 +1,114 @@ +## JavaScript Ruleset Format + +Spectral v6.0 added support for a JavaScript ruleset format, similar to the JSON and YAML formats. + +This has a few benefits: + +- It lets you explicitly load formats or rulesets to get control over versioning. +- You can load common functions from popular JS libraries. +- It feels a lot more welcoming to developers experienced with JavaScript, especially when it comes to working with custom functions. + +**Example** + +To create a JavaScript ruleset, the first step is creating a folder. In your terminal, run the following commands: + +``` +mkdir style-guide +cd style-guide +``` + +Next, install two dependencies using [npm](https://www.npmjs.com/): + +``` +npm install --save @stoplight/spectral-functions +npm install --save @stoplight/spectral-formats +``` + +Installing these packages is not required for creating a JavaScript ruleset, but you'll use them in the example to create some common rules used with Spectral and to target a specific OpenAPI format. + +Next, create a JavaScript file to hold your ruleset: + +``` +touch spectral.js +``` + +Inside the file, create a couple of rules: + +```js +import { truthy, undefined as pattern, schema } from "@stoplight/spectral-functions"; +import { oas3 } from "@stoplight/spectral-formats"; + +export default { + rules: { + "api-home-get": { + description: "APIs root path (`/`) MUST have a GET operation.", + message: "Otherwise people won't know how to get it.", + given: "$.paths[/]", + then: { + field: "get", + function: truthy, + }, + severity: "warn", + }, + + // Author: Phil Sturgeon (https://github.com/philsturgeon) + "no-numeric-ids": { + description: "Avoid exposing IDs as an integer, UUIDs are preferred.", + given: '$.paths..parameters[*].[?(@property === "name" && (@ === "id" || @.match(/(_id|Id)$/)))]^.schema', + then: { + function: schema, + functionOptions: { + schema: { + type: "object", + not: { + properties: { + type: { + const: "integer", + }, + }, + }, + properties: { + format: { + const: "uuid", + }, + }, + }, + }, + }, + severity: "error", + }, + + // Author: Nauman Ali (https://github.com/naumanali-stoplight) + "no-global-versioning": { + description: "Using global versions just forces all your clients to do a lot more work for each upgrade. Please consider using API Evolution instead.", + message: "Server URL should not contain global versions", + given: "$.servers[*].url", + then: { + function: pattern, + functionOptions: { + notMatch: "/v[1-9]", + }, + }, + formats: [oas3], + severity: "warn", + }, + }, +}; +``` + +If you use custom functions, the keywords `functions` and `functionOptions` have been removed, as they were designed to help Spectral find your functions. Now functions are passed as a variable, instead of using a string that contains the name like the JSON/YAML formats. + +This code example should look fairly familiar for anyone who has used the JSON or YAML formats. The next steps for using this ruleset would be publishing it as an npm package, and then installing that package as part of your API project and referencing in your Spectral ruleset as: + +``` +extends: ["@your-js-ruleset"] +``` + +Or using unpkg: + +``` +extends: + - https://unpkg.com/@your-js-ruleset +``` + +For a more detailed example of creating a JavaScript ruleset and publishing it to npm, check out [Distribute Spectral Style Guides with npm](https://apisyouwonthate.com/blog/distribute-spectral-style-guides-with-npm) at APIs You Won't Hate. diff --git a/toc.json b/toc.json index b65c935e9..bb3bb6653 100644 --- a/toc.json +++ b/toc.json @@ -21,7 +21,7 @@ }, { "type": "item", - "title": "Rulesets", + "title": "Create a Ruleset", "uri": "/docs/getting-started/3-rulesets.md" }, { @@ -54,9 +54,45 @@ "uri": "/docs/guides/3-javascript.md" }, { - "type": "item", - "title": "Custom Rulesets", - "uri": "/docs/guides/4-custom-rulesets.md" + "type": "group", + "title": "Rulesets", + "items": [ + { + "type": "item", + "title": "Overview", + "uri": "/docs/guides/4-custom-rulesets.md" + }, + { + "type": "item", + "title": "Rules", + "uri": "/docs/guides/4a-rules.md" + }, + { + "type": "item", + "title": "Extends", + "uri": "/docs/guides/4b-extends.md" + }, + { + "type": "item", + "title": "Aliases", + "uri": "/docs/guides/4c-aliases.md" + }, + { + "type": "item", + "title": "Overrides", + "uri": "/docs/guides/4d-overrides.md" + }, + { + "type": "item", + "title": "Recommended", + "uri": "/docs/guides/4e-recommended.md" + }, + { + "type": "item", + "title": "JavaScript Ruleset Format", + "uri": "/docs/guides/4f-js-rulesets.md" + } + ] }, { "type": "item",