Skip to content

roas-asyncapi: AsyncAPI v2.6 model + validation - #242

Open
SVilgelm wants to merge 4 commits into
mainfrom
feat/asyncapi-v2_6
Open

roas-asyncapi: AsyncAPI v2.6 model + validation#242
SVilgelm wants to merge 4 commits into
mainfrom
feat/asyncapi-v2_6

Conversation

@SVilgelm

@SVilgelm SVilgelm commented Aug 3, 2026

Copy link
Copy Markdown
Member

Third of the AsyncAPI PRs (3.0 in #238, 3.1 in #239). Adds the v2_6 feature — off by default, since v3_1 stays the default.

2.6 is a different document, not an earlier draft of v3, so this module is written rather than derived from v3_0. What changed between them:

2.6 3.x
Channels required, keyed by the channel path keyed by a name, with a separate address
Operations publish / subscribe under a channel, from the consumer's point of view a top-level map, send / receive from the application's point of view
Messages one message, or { "oneOf": [...] }, on the operation the channel's messages map, referenced by the operation
Payload dialect schemaFormat on the message, payload alongside it a Multi Format Schema Object wrapping both
Parameters carry a full Schema strings constrained by enum / default / examples
Security OpenAPI-style requirement maps (name → scopes) inline schemes; OAuth's scopes renamed availableScopes
tags / externalDocs at the document root under info
Servers one url host + pathname

The one shared piece is the Schema Object — draft-07 plus AsyncAPI's additions — which is the same in both, minus v3's Multi Format wrapper.

Validation carries over the per-object checks (runtime expressions, schema keyword constraints, security-scheme variants, channel path ↔ parameters) and adds what only the root can see in this shape:

  • a channel's servers are plain names here, not $refs, so they are resolved against the root servers map;
  • operationId must be unique across every operation in the document — including between one channel's own publish and subscribe — and the diagnostic names the first use;
  • messageId likewise;
  • a security requirement must name a declared scheme, and may list scopes only where the scheme's type takes them (oauth2 / openIdConnect), which is the check the v3 shape does not need.

Document::operations() enumerates every (path, kind, operation) triple, which is both useful on its own and the traversal the 2.6 → 3.0 conversion will need.

The publish / subscribe inversion is documented wherever it could mislead — module docs, the OperationKind variants, and the README — since mapping publish to send is the classic migration bug, and that conversion is the next PR.

Verified across every feature combination in isolation: v2_6 only (140 tests), v3_0 only (164), v3_1 only (163), --all-features (398), workspace 2546. Clippy -D warnings and RUSTDOCFLAGS="-D warnings" cargo doc clean under default, all-features, and v2_6-only. Coverage on the crate is 99.75% lines / 100% functions.

The pre-v3 model, written rather than derived from a version module:
channels required and keyed by path, publish/subscribe operations named
from the consumer's point of view, an operation message that may be a
oneOf set, schemaFormat on the message itself, parameters carrying full
schemas, OpenAPI-style security requirements, and tags/externalDocs at
the root.

Validation adds what only the root can see: channel servers naming
declared servers, operationId and messageId uniqueness across the
document, and security requirements naming a declared scheme with
scopes only where the scheme type allows them.

Assisted-by: Claude Opus 5
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Copilot AI review requested due to automatic review settings August 3, 2026 20:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

v2.6 messageId uniqueness validation currently only considers components.messages and misses inline operation messages, allowing invalid documents to pass validation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR adds first-class AsyncAPI v2.6 support to roas-asyncapi behind a new, non-default v2_6 feature, including a full v2.6 object model, validation, and fixtures/tests, plus docs and Codecov component updates to reflect the newly supported version.

Changes:

  • Introduces a new v2_6 module (model + per-object and root-level validation) gated by the v2_6 Cargo feature.
  • Adds v2.6 integration tests and fixtures (JSON/YAML) covering parsing, validation, and key cross-object checks.
  • Updates repository/crate READMEs and Codecov component configuration to include v2.6.
File summaries
File Description
README.md Updates top-level docs to list AsyncAPI v2.6 support.
crates/roas-asyncapi/tests/v2_6_test.rs Adds v2.6 integration tests for parsing/validation and key invariants.
crates/roas-asyncapi/tests/v2_6_data/streetlights.yaml Adds a representative v2.6 YAML fixture exercising key model differences.
crates/roas-asyncapi/tests/v2_6_data/minimal.json Adds a minimal v2.6 JSON fixture.
crates/roas-asyncapi/tests/v2_6_data/bad_wiring.json Adds a negative fixture for wiring/uniqueness/security validation.
crates/roas-asyncapi/tests/v2_6_data/bad_parameters.json Adds a negative fixture for channel-path/parameter validation.
crates/roas-asyncapi/src/v2_6/mod.rs Introduces the v2.6 module structure and public re-exports.
crates/roas-asyncapi/src/v2_6/version.rs Adds a strict v2.6 asyncapi version newtype (parse-time rejection).
crates/roas-asyncapi/src/v2_6/tag.rs Implements the v2.6 Tag object + validation.
crates/roas-asyncapi/src/v2_6/server.rs Implements v2.6 Server/ServerVariable + URL placeholder validation.
crates/roas-asyncapi/src/v2_6/security_scheme.rs Implements v2.6 security schemes/requirements + variant validation rules.
crates/roas-asyncapi/src/v2_6/schema.rs Implements the v2.6 Schema Object (draft-07 + AsyncAPI additions) + keyword constraints.
crates/roas-asyncapi/src/v2_6/parameter.rs Implements v2.6 Parameter object (incl. runtime expression validation).
crates/roas-asyncapi/src/v2_6/operation.rs Implements v2.6 Operation/OperationTrait + consumer-view publish/subscribe semantics.
crates/roas-asyncapi/src/v2_6/message.rs Implements v2.6 Message/traits/examples and OperationMessage (oneOf vs single) handling.
crates/roas-asyncapi/src/v2_6/info.rs Implements v2.6 Info/Contact/License objects + validation.
crates/roas-asyncapi/src/v2_6/external_documentation.rs Implements v2.6 External Documentation object + validation.
crates/roas-asyncapi/src/v2_6/document.rs Adds the v2.6 root document model and root-level validation (wiring, uniqueness, security checks).
crates/roas-asyncapi/src/v2_6/correlation_id.rs Implements v2.6 CorrelationId object + runtime expression validation.
crates/roas-asyncapi/src/v2_6/components.rs Implements v2.6 Components maps + key validation.
crates/roas-asyncapi/src/v2_6/channel_item.rs Implements v2.6 ChannelItem + path-placeholder/parameter consistency checks.
crates/roas-asyncapi/src/lib.rs Exposes the v2_6 module behind the new Cargo feature and updates crate docs.
crates/roas-asyncapi/README.md Updates crate README to document v2.6 support and its key differences vs v3.x.
crates/roas-asyncapi/Cargo.toml Adds the v2_6 feature flag (off by default).
.github/codecov.yml Adds a Codecov component for v2.6-specific code and tests.
Review details
  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread crates/roas-asyncapi/src/v2_6/document.rs Outdated
Review fixes:
- a default-dialect payload is an AsyncAPI Schema Object, so it is now
  parsed and validated as one; a payload in a named foreign dialect
  stays raw
- schema-valued fields accept the boolean form draft-07 allows
  (components.schemas, parameter.schema, message/trait headers)
- a message `oneOf` recurses into the whole message definition, so a
  nested set no longer reserializes as {}
- messageId uniqueness spans the document: component and inline
  messages, walking through oneOf sets
- document-local channel $refs resolve, with cycle protection, so a
  referenced channel gets the server, security, path-parameter and
  operationId checks
- Reference is allowed per field rather than uniformly: bindings,
  externalDocs, tags and most component maps take the object directly,
  and Server has no externalDocs field in 2.6 at all
- uniqueItems on tag collections and ServerVariable.enum

Assisted-by: Claude Opus 5
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Copilot AI review requested due to automatic review settings August 4, 2026 00:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The v2.6 messageId uniqueness diagnostic does not include the “first use” location described in the PR text, so either the implementation or the description should be updated for consistency.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

crates/roas-asyncapi/src/v2_6/document.rs:316

  • The PR description says messageId duplicates should name the first use, but this diagnostic only reports a generic "duplicate messageId …" with no location/context for the original occurrence. That makes it harder to fix large documents and doesn’t match the stated behavior.

Consider tracking the first-seen location (e.g., component key or #.channels.<path>.<publish|subscribe>.message… traversal path) similarly to the operationId uniqueness check, and include it in the error message (or update the PR description if the generic message is intentional).

            if !message_id.is_empty() && !message_ids.insert(message_id) {
                ctx.error_field(
                    "messageId",
                    format!("duplicate messageId `{message_id}` in the document"),
                );
  • Files reviewed: 25/25 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Review fixes:
- restore RefOr on components.servers / serverVariables / parameters:
  they delegate to definitions that do allow a Reference, which the
  earlier per-field check missed by not following the delegation
- $ref is a *field* of a Channel Item and a keyword of a Schema Object,
  so both now model it as one and keep their siblings through a
  round-trip
- resolve every legal local channel pointer, root as well as component,
  and drop the hop limit the visited set already made redundant
- follow references before judging: a referenced security scheme is
  resolved to its real type before the scope check, and an operation
  message $ref must name a declared component message
- message and trait headers must describe an object
- tag uniqueness compares JSON instances, so tags differing only by
  `x-order: 1` vs `1.0` collide

Assisted-by: Claude Opus 5
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Copilot AI review requested due to automatic review settings August 4, 2026 01:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Replaces the ad-hoc lookups with one resolution layer that reports why
a pointer failed — missing, cyclic, wrong kind, or terminating outside
the document — and applies it to channels, messages, security schemes,
and servers alike.

Review fixes:
- $ref siblings on a channel item are validated and contribute their
  operations, not just the resolved target's
- message pointers resolve recursively: component aliases follow their
  chain, and `#/channels/<path>/publish/message[/oneOf/<i>]` works
- a security alias that leads nowhere no longer counts as declared
- headers `type` must be the string `object`; the list form never
  satisfies the schema's `const`
- a chain ending in an external reference is accepted rather than
  reported as unresolved
- referenced and component servers get the security cross-check
- root channel pointers require RFC 6901 escaping, so
  `#/channels/source/path` no longer resolves the key `source/path`

Assisted-by: Claude Opus 5
Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
Copilot AI review requested due to automatic review settings August 4, 2026 02:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants