roas-asyncapi: AsyncAPI v2.6 model + validation - #242
Conversation
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>
There was a problem hiding this comment.
🟡 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_6module (model + per-object and root-level validation) gated by thev2_6Cargo 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.
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>
There was a problem hiding this comment.
🟡 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
messageIdduplicates 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>
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>
Third of the AsyncAPI PRs (3.0 in #238, 3.1 in #239). Adds the
v2_6feature — off by default, sincev3_1stays 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:addresspublish/subscribeunder a channel, from the consumer's point of viewsend/receivefrom the application's point of view{ "oneOf": [...] }, on the operationmessagesmap, referenced by the operationschemaFormaton the message, payload alongside itSchemaenum/default/examplesscopesrenamedavailableScopestags/externalDocsinfourlhost+pathnameThe 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:
serversare plain names here, not$refs, so they are resolved against the rootserversmap;operationIdmust be unique across every operation in the document — including between one channel's ownpublishandsubscribe— and the diagnostic names the first use;messageIdlikewise;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/subscribeinversion is documented wherever it could mislead — module docs, theOperationKindvariants, and the README — since mappingpublishtosendis the classic migration bug, and that conversion is the next PR.Verified across every feature combination in isolation:
v2_6only (140 tests),v3_0only (164),v3_1only (163),--all-features(398), workspace 2546. Clippy-D warningsandRUSTDOCFLAGS="-D warnings" cargo docclean under default, all-features, andv2_6-only. Coverage on the crate is 99.75% lines / 100% functions.