Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[![CircleCI](https://img.shields.io/circleci/build/github/stoplightio/spectral/develop)](https://circleci.com/gh/stoplightio/spectral) [![npm Downloads](https://img.shields.io/npm/dw/@stoplight/spectral-core?color=blue)](https://www.npmjs.com/package/@stoplight/spectral-core) [![Stoplight Forest](https://img.shields.io/ecologi/trees/stoplightinc)][stoplight_forest]

- **Custom Rulesets**: Create custom rules to lint JSON or YAML objects
- **Ready-to-use Rulesets**: Validate and lint **OpenAPI v2 & v3.x**, **AsyncAPI**, and **Arazzo v1** Documents
- **Ready-to-use Rulesets**: Validate and lint **OpenAPI v2, v3.0, v3.1 & v3.2**, **AsyncAPI**, and **Arazzo v1** Documents
- **API Style Guides**: Automated [API Style Guides](https://stoplight.io/api-style-guides-guidelines-and-best-practices?utm_source=github.com&utm_medium=referral&utm_campaign=github_repo_spectral) using rulesets improve consistency across all your APIs
- **Ready-to-use Functions**: Built-in set of functions to help [create custom rules](https://meta.stoplight.io/docs/spectral/e5b9616d6d50c-custom-rulesets#adding-rules). Functions include pattern checks, parameter checks, alphabetical ordering, a specified number of characters, provided keys are present in an object, etc.
- **Custom Functions**: Create custom functions for advanced use cases
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/4-openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

Spectral has a built-in [OpenAPI Specification](https://openapis.org/specification) ruleset that you can use to validate your OpenAPI files.

Add `extends: "spectral:oas"` ("oas" being shorthand for OpenAPI Specification) to your ruleset file to apply rules for OpenAPI v2 and v3.x, depending on the appropriate OpenAPI version being used (this is automatically detected through [formats](../getting-started/3-rulesets.md#formats)).
Add `extends: "spectral:oas"` ("oas" being shorthand for OpenAPI Specification) to your ruleset file to apply rules for OpenAPI v2, v3.0, v3.1, and v3.2, depending on the appropriate OpenAPI version being used (this is automatically detected through [formats](../getting-started/3-rulesets.md#formats)).

You can see a full list of the rules in this ruleset in [OpenAPI Rules](../reference/openapi-rules.md).
1 change: 1 addition & 0 deletions docs/guides/4-custom-rulesets.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Formats are an optional way to specify which API description formats a rule, or
- `oas3` (OpenAPI v3.x)
- `oas3_0` (OpenAPI v3.0.x)
- `oas3_1` (OpenAPI v3.1.x)
- `oas3_2` (OpenAPI v3.2.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)
Expand Down
51 changes: 45 additions & 6 deletions docs/reference/openapi-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ In your ruleset file you can add `extends: "spectral:oas"` and you'll get all of

## OpenAPI v2 & v3

These rules apply to both OpenAPI v2.0, v3.0, and most likely v3.1, although there are some differences.
These rules apply to OpenAPI v2.0, v3.0, v3.1, and v3.2, although there are some differences.

### contact-properties

Expand Down Expand Up @@ -635,7 +635,7 @@ paths:

## OpenAPI v3-only

These rules will only be applied to OpenAPI v3.0 documents.
These rules will only be applied to OpenAPI v3.0, v3.1, and v3.2 documents.

### oas3-api-servers

Expand Down Expand Up @@ -696,7 +696,7 @@ Parameter objects should have a `description`.
### oas3-schema

Validate structure of OpenAPI v3 specification.
If OpenAPI 3.1.0 is used, `jsonSchemaDialect` is not respected and the draft 2020-12 is applied.
If OpenAPI 3.1 or 3.2 is used, `jsonSchemaDialect` is not respected and the draft 2020-12 is applied.
If you define your own `jsonSchemaDialect`, you'll most likely want to disable this rule.

**Recommended:** Yes
Expand Down Expand Up @@ -880,7 +880,7 @@ schemas:

### oas3-server-variables

This rule ensures that server variables defined in OpenAPI Specification 3 (OAS3) and 3.1 are valid, not unused, and result in a valid URL. Properly defining and using server variables is crucial for the accurate representation of API endpoints and preventing potential misconfigurations or security issues.
This rule ensures that server variables defined in OpenAPI Specification 3 (OAS3), 3.1, and 3.2 are valid, not unused, and result in a valid URL. Properly defining and using server variables is crucial for the accurate representation of API endpoints and preventing potential misconfigurations or security issues.

**Recommended**: Yes

Expand Down Expand Up @@ -975,7 +975,7 @@ paths:

### oas3_1-servers-in-webhook

Servers should not be defined in a webhook.
Servers should not be defined in a webhook. Applies to OpenAPI v3.1 and v3.2.

**Recommended:** Yes

Expand Down Expand Up @@ -1004,7 +1004,7 @@ webhooks:

### oas3_1-callbacks-in-webhook

Callbacks should not be defined in a webhook.
Callbacks should not be defined in a webhook. Applies to OpenAPI v3.1 and v3.2.

**Recommended:** Yes

Expand All @@ -1016,3 +1016,42 @@ webhooks:
post:
callbacks: ...
```

## OpenAPI v3.2-only

These rules will only be applied to OpenAPI v3.2 documents.

### oas3_2-no-deprecated-xml-attribute

The `xml.attribute` field is deprecated in OpenAPI v3.2. Use `xml.nodeType: attribute` instead.

Only `xml.attribute: true` is flagged. `xml.attribute: false` is not flagged, since it matches the
default `nodeType` behavior and has no corresponding migration action.

**Recommended:** Yes

**Bad Example**

```yaml
components:
schemas:
Pet:
properties:
name:
type: string
xml:
attribute: true
```

**Good Example**

```yaml
components:
schemas:
Pet:
properties:
name:
type: string
xml:
nodeType: attribute
```
16 changes: 13 additions & 3 deletions packages/formats/src/__tests__/openapi.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { oas2, oas3, oas3_0, oas3_1 } from '../openapi';
import { oas2, oas3, oas3_0, oas3_1, oas3_2 } from '../openapi';

describe('OpenAPI format', () => {
describe('OpenAPI 2.0 aka Swagger', () => {
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('OpenAPI format', () => {
expect(oas3_0({ openapi: version }, null)).toBe(true);
});

it.each(['3', '3.1', '3.1.0', '3.1.3'])('does not recognize %s version', version => {
it.each(['3', '3.1', '3.1.0', '3.1.3', '3.2', '3.2.0'])('does not recognize %s version', version => {
expect(oas3_0({ openapi: version }, null)).toBe(false);
});
});
Expand All @@ -48,8 +48,18 @@ describe('OpenAPI format', () => {
expect(oas3_1({ openapi: version }, null)).toBe(true);
});

it.each(['3', '3.0', '3.0.3', '3.0.0'])('does not recognize %s version', version => {
it.each(['3', '3.0', '3.0.3', '3.0.0', '3.2', '3.2.0'])('does not recognize %s version', version => {
expect(oas3_1({ openapi: version }, null)).toBe(false);
});
});

describe('OpenAPI 3.2', () => {
it.each(['3.2.0', '3.2', '3.2.1'])('recognizes %s version correctly', version => {
expect(oas3_2({ openapi: version }, null)).toBe(true);
});

it.each(['3', '3.0', '3.0.3', '3.0.0', '3.1', '3.1.0'])('does not recognize %s version', version => {
expect(oas3_2({ openapi: version }, null)).toBe(false);
});
});
});
4 changes: 4 additions & 0 deletions packages/formats/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ oas3_0.displayName = 'OpenAPI 3.0.x';
export const oas3_1: Format = (document: unknown): boolean =>
isOas3(document) && /^3\.1(?:\.[0-9]*)?$/.test(String(document.openapi));
oas3_1.displayName = 'OpenAPI 3.1.x';

export const oas3_2: Format = (document: unknown): boolean =>
isOas3(document) && /^3\.2(?:\.[0-9]*)?$/.test(String(document.openapi));
oas3_2.displayName = 'OpenAPI 3.2.x';
4 changes: 4 additions & 0 deletions packages/rulesets/scripts/compile-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const schemas = [
'oas/schemas/oas/v3.1/dialect.schema.json',
'oas/schemas/oas/v3.1/meta.schema.json',
'oas/schemas/oas/v3.1/index.json',
'oas/schemas/oas/v3.2/dialect.schema.json',
'oas/schemas/oas/v3.2/meta.schema.json',
'oas/schemas/oas/v3.2/index.json',
'arazzo/schemas/arazzo/v1.0/index.json',
].map(async schema => JSON.parse(await fs.promises.readFile(path.join(cwd, schema), 'utf8')));

Expand Down Expand Up @@ -57,6 +60,7 @@ Promise.all(schemas)
oas2_0: 'http://swagger.io/v2/schema.json',
oas3_0: 'https://spec.openapis.org/oas/3.0/schema/2019-04-02',
oas3_1: 'https://spec.openapis.org/oas/3.1/schema/2021-09-28',
oas3_2: 'https://spec.openapis.org/oas/3.2/schema/2025-11-23',
arazzo1_0: 'https://spec.openapis.org/arazzo/1.0/schema/2024-08-01',
});

Expand Down
2 changes: 1 addition & 1 deletion packages/rulesets/src/arazzo/schemas/validators.ts

Large diffs are not rendered by default.

201 changes: 201 additions & 0 deletions packages/rulesets/src/oas/__tests__/oas3-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,207 @@ testRule('oas3-schema', [
],
},

{
name: 'oas3.2: jsonSchemaDialect',
document: {
openapi: '3.2.0',
info: {
title: 'Example jsonSchemaDialect error',
version: '1.0.0',
},
paths: {},
jsonSchemaDialect: null,
},
errors: [
{
message: '"jsonSchemaDialect" property must be string.',
path: ['jsonSchemaDialect'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'oas3.2: missing webhooks/components/paths',
document: {
openapi: '3.2.0',
info: {
title: 'Missing webhooks/components/paths',
version: '1.0.0',
},
},
errors: [
{
message: 'The document must have either "paths", "webhooks" or "components".',
path: [],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'oas3.2: validate schemas',
document: {
openapi: '3.2.0',
info: {
title: 'our-api',
version: '1.0',
},
paths: {
'/config': {
parameters: [
{
schema: null,
name: 'id',
in: 'query',
required: false,
description: 'Id of an existing config.',
},
],
get: {
summary: 'Get User Info by User ID',
operationId: 'get-users-settings',
responses: {
'200': {
description: 'Settings for User Found',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
key: {
type: 'string,',
},
value: {
type: 'string',
},
},
required: ['key', 'value'],
},
},
},
},
},
},
},
},
components: {
schemas: {
Schema: {
type: 'object',
additionalProperties: {
type: 'int',
},
},
Schema_2: {
type: 'object',
additionalProperties: 'invalid',
},
},
},
},
errors: [
{
message: '"schema" property must be object,boolean.',
path: ['paths', '/config', 'parameters', '0', 'schema'],
severity: DiagnosticSeverity.Error,
},
{
message:
'"type" property must be equal to one of the allowed values: "array", "boolean", "integer", "null", "number", "object", "string". Did you mean "string"?.',
path: [
'paths',
'/config',
'get',
'responses',
'200',
'content',
'application/json',
'schema',
'properties',
'key',
'type',
],
},
{
message: '"type" property must be array.',
path: [
'paths',
'/config',
'get',
'responses',
'200',
'content',
'application/json',
'schema',
'properties',
'key',
'type',
],
},
{
message: '"type" property must match a schema in anyOf.',
path: [
'paths',
'/config',
'get',
'responses',
'200',
'content',
'application/json',
'schema',
'properties',
'key',
'type',
],
},
{
message:
'"type" property must be equal to one of the allowed values: "array", "boolean", "integer", "null", "number", "object", "string". Did you mean "integer"?.',
path: ['components', 'schemas', 'Schema', 'additionalProperties', 'type'],
severity: DiagnosticSeverity.Error,
},
{
message: '"type" property must be array.',
path: ['components', 'schemas', 'Schema', 'additionalProperties', 'type'],
severity: DiagnosticSeverity.Error,
},
{
message: '"type" property must match a schema in anyOf.',
path: ['components', 'schemas', 'Schema', 'additionalProperties', 'type'],
severity: DiagnosticSeverity.Error,
},
{
message: '"additionalProperties" property must be object,boolean.',
path: ['components', 'schemas', 'Schema_2', 'additionalProperties'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'oas3.2: valid document',
document: {
openapi: '3.2.0',
info: {
title: 'Example valid 3.2 document',
version: '1.0.0',
},
paths: {
'/pets': {
get: {
responses: {
'200': {
description: 'A paged array of pets',
},
},
},
},
},
},
errors: [],
},

{
name: 'oas3.1: uri template as server url',
document: {
Expand Down
Loading
Loading