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
7 changes: 7 additions & 0 deletions .changeset/new-parks-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@redocly/openapi-core': minor
'@redocly/cli': minor
---

Added a new built-in rule `spec-ref-targets` that requires AsyncAPI 3 `$ref`s to point to the locations the specification mandates.
The rule checks that operation and reply `channel` references point to root channels, their `messages` references point to the referenced channel's messages, and channel `servers` references point to root servers.
122 changes: 122 additions & 0 deletions docs/@v2/rules/async/spec-ref-targets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
slug: /docs/cli/rules/async/spec-ref-targets
---

# spec-ref-targets

Requires that `$ref`s in operations, replies, and channels point to the targets the AsyncAPI 3.0 specification requires.

| AsyncAPI | Compatibility |
| -------- | ------------- |
| 2.6 | ❌ |
| 3.0 | ✅ |
| 3.1 | ✅ |

The rule checks the `$ref` targets that AsyncAPI 3.0 restricts:

- An operation's `channel` must reference a channel from the root `channels` object (`#/channels/<name>`), not from `components`.
- An operation's `messages` must reference messages of the operation's referenced channel (`<channel-ref>/messages/<name>`), never `components.messages` directly.
- An operation reply's `channel` and `messages` follow the same requirements, with `messages` checked against the reply's channel.
- A channel's `servers` must reference servers from the root `servers` object (`#/servers/<name>`).

Some cases are exempt from these requirements:

- Operations and replies defined in `components` may reference any channel — the specification restricts only the ones defined in the root `operations` object.
- Channels defined in `components.channels` may reference any server.
- `$ref`s that point to other files are skipped: in multi-file documents they become internal pointers only after bundling.

## API design principles

These are MUST requirements in the AsyncAPI 3.0 specification — see the Operation Object, Operation Reply Object, and Channel Object sections.
A document that breaks them still resolves every `$ref`, so the mistake is easy to miss, but AsyncAPI tooling and renderers reject such documents.
This rule reports the misplaced references so you can fix them before they break downstream tools.

## Configuration

| Option | Type | Description |
| -------- | ------ | ----------------------------------------------------------------------------------------- |
| severity | string | Possible values: `off`, `warn`, `error`. Default `warn` (in `recommended` configuration). |

An example configuration:

```yaml
rules:
spec-ref-targets: error
```

## Examples

Given this configuration:

```yaml
rules:
spec-ref-targets: error
```

Example of an **incorrect** operation — its `channel` and `messages` reference `components` directly:

```yaml
asyncapi: 3.0.0
info:
title: Ping service
version: 1.0.0
channels:
ping:
$ref: '#/components/channels/ping'
operations:
sendPing:
action: send
channel:
$ref: '#/components/channels/ping'
messages:
- $ref: '#/components/messages/ping'
components:
channels:
ping:
address: ping
messages:
ping:
$ref: '#/components/messages/ping'
messages:
ping:
payload:
type: string
```

Example of a **correct** operation — it references the root channel and that channel's messages:

```yaml
asyncapi: 3.0.0
info:
title: Ping service
version: 1.0.0
channels:
ping:
address: ping
messages:
ping:
$ref: '#/components/messages/ping'
operations:
sendPing:
action: send
channel:
$ref: '#/channels/ping'
messages:
- $ref: '#/channels/ping/messages/ping'
components:
messages:
ping:
payload:
type: string
```

## Related rules

- [no-channel-trailing-slash](./no-channel-trailing-slash.md)
- [channels-kebab-case](./channels-kebab-case.md)
- [struct](../common/struct.md)

## Resources

- [Rule source](https://github.com/Redocly/redocly-cli/blob/main/packages/core/src/rules/async3/spec-ref-targets.ts)
- [AsyncAPI 3.0 Operation Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#operationObject)
1 change: 1 addition & 0 deletions docs/@v2/rules/built-in-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Other rules, such as the `struct` and `info.*`, also apply to AsyncAPI.

- [channels-kebab-case](./async/channels-kebab-case.md): Channels must be in `kebab-case` format
- [no-channel-trailing-slash](./async/no-channel-trailing-slash.md): No trailing slashes on channels
- [spec-ref-targets](./async/spec-ref-targets.md): Operation, reply, and channel `$ref`s must point to the targets the AsyncAPI 3.0 specification requires

## Arazzo rules

Expand Down
1 change: 1 addition & 0 deletions docs/@v2/rules/recommended.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Warnings:
- [requestBody-replacements-unique](./arazzo/requestBody-replacements-unique.md)
- [security-scopes-defined](./common/security-scopes-defined.md)
- [spec-discriminator-defaultMapping](./oas/spec-discriminator-defaultMapping.md)
- [spec-ref-targets](./async/spec-ref-targets.md)
- [step-onFailure-unique](./arazzo/step-onFailure-unique.md)
- [step-onSuccess-unique](./arazzo/step-onSuccess-unique.md)
- [tag-description](./oas/tag-description.md)
Expand Down
1 change: 1 addition & 0 deletions docs/@v2/rules/ruleset-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ rules:
no-schema-type-mismatch: error
operation-operationId: warn
security-scopes-defined: warn
spec-ref-targets: warn
struct: error
tag-description: warn
```
Expand Down
1 change: 1 addition & 0 deletions docs/@v2/rules/spec-ruleset.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ All the rules are of severity `error`:
- [spec-example-values](./oas/spec-example-values.md)
- [spec-no-invalid-encoding-combinations](./oas/spec-no-invalid-encoding-combinations.md)
- [spec-no-invalid-tag-parents](./oas/spec-no-invalid-tag-parents.md)
- [spec-ref-targets](./async/spec-ref-targets.md)
- [spec-strict-refs](./oas/spec-strict-refs.md)
- [spec-step-mutually-exclusive-fields](./arazzo/spec-step-mutually-exclusive-fields.md)
- [step-onFailure-unique](./arazzo/step-onFailure-unique.md)
Expand Down
1 change: 1 addition & 0 deletions docs/@v2/v2.sidebars.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
- separator: AsyncAPI
- page: rules/async/channels-kebab-case.md
- page: rules/async/no-channel-trailing-slash.md
- page: rules/async/spec-ref-targets.md
- separator: Open-RPC
- page: rules/openrpc/spec-no-duplicated-method-params.md
- page: rules/openrpc/spec-no-required-params-after-optional.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ exports[`resolveConfig > should ignore minimal from the root and read local file
"no-schema-type-mismatch": "error",
"operation-operationId": "warn",
"security-scopes-defined": "warn",
"spec-ref-targets": "warn",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down Expand Up @@ -508,6 +509,7 @@ exports[`resolveConfig > should resolve extends with local file config which con
"no-schema-type-mismatch": "error",
"operation-operationId": "warn",
"security-scopes-defined": "warn",
"spec-ref-targets": "warn",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/config/__tests__/load.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ describe('loadConfig', () => {
"no-schema-type-mismatch": "warn",
"operation-operationId": "warn",
"security-scopes-defined": "off",
"spec-ref-targets": "off",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down Expand Up @@ -568,6 +569,7 @@ describe('loadConfig', () => {
"no-schema-type-mismatch": "error",
"operation-operationId": "warn",
"security-scopes-defined": "warn",
"spec-ref-targets": "warn",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down Expand Up @@ -933,6 +935,7 @@ describe('loadConfig', () => {
"no-schema-type-mismatch": "warn",
"operation-operationId": "warn",
"security-scopes-defined": "off",
"spec-ref-targets": "off",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down Expand Up @@ -1382,6 +1385,7 @@ describe('loadConfig', () => {
"no-schema-type-mismatch": "warn",
"operation-operationId": "warn",
"security-scopes-defined": "off",
"spec-ref-targets": "off",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down Expand Up @@ -1742,6 +1746,7 @@ describe('loadConfig', () => {
"no-schema-type-mismatch": "error",
"operation-operationId": "warn",
"security-scopes-defined": "warn",
"spec-ref-targets": "warn",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down Expand Up @@ -2107,6 +2112,7 @@ describe('loadConfig', () => {
"no-schema-type-mismatch": "warn",
"operation-operationId": "warn",
"security-scopes-defined": "off",
"spec-ref-targets": "off",
"tag-description": "warn",
"tags-alphabetical": "off",
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/config/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ const all: RawGovernanceConfig<'built-in'> = {
'no-channel-trailing-slash': 'error',
'operation-operationId': 'error',
'security-scopes-defined': 'error',
'spec-ref-targets': 'error',
'tag-description': 'error',
'tags-alphabetical': 'error',
'no-duplicated-tag-names': 'error',
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/config/minimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ const minimal: RawGovernanceConfig<'built-in'> = {
'no-schema-type-mismatch': 'warn',
'operation-operationId': 'warn',
'security-scopes-defined': 'off',
'spec-ref-targets': 'off',
'tag-description': 'warn',
'tags-alphabetical': 'off',
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/config/recommended-strict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ const recommendedStrict: RawGovernanceConfig<'built-in'> = {
'no-schema-type-mismatch': 'error',
'operation-operationId': 'error',
'security-scopes-defined': 'error',
'spec-ref-targets': 'error',
'tag-description': 'error',
'tags-alphabetical': 'off',
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/config/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ const recommended: RawGovernanceConfig<'built-in'> = {
'no-schema-type-mismatch': 'error',
'operation-operationId': 'warn',
'security-scopes-defined': 'warn',
'spec-ref-targets': 'warn',
'tag-description': 'warn',
'tags-alphabetical': 'off',
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/config/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ const spec: RawGovernanceConfig<'built-in'> = {
'no-schema-type-mismatch': 'off',
'operation-operationId': 'off',
'security-scopes-defined': 'off',
'spec-ref-targets': 'error',
'tag-description': 'off',
'tags-alphabetical': 'off',
},
Expand Down
Loading
Loading