Skip to content

Commit c686e65

Browse files
authored
docs: document reference escaping, writing and version conversion (#64)
- references: local references are JSON pointers in a URI, so "/" and "~" in a key are escaped as "~1" and "~0", and the pointer is percent-decoded; add how references are written, including inlining references whose target the written document does not have. - version conversion: list which references stay references and which are written inline, how V2 channel references map to V3 channel keys, and the operations and messages a downgrade drops. - README: drop the JsonPointer.Net attribution, which is no longer a dependency, and fix the ReferenceResolutionSetting name and a server reference example.
1 parent 9149a76 commit c686e65

3 files changed

Lines changed: 40 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,13 @@ All readers will write warnings and errors to the diagnostics.
129129

130130

131131
## Reference Resolution
132-
Internal references are resolved by default. This includes component and non-component references e.g `#/components/messages/MyMessage` and `#/servers/0`.
132+
Internal references are resolved by default. This includes component and non-component references e.g `#/components/messages/MyMessage` and `#/servers/production`.
133133
External references can be resolved by setting `ReferenceResolution` to `ResolveAllReferences`.
134134
The default implementation will resolve anything prefixed with `file://`, `http://` & `https://`, however a custom implementation can be made, by inhereting from the `IStreamLoader` interface and setting the `ExternalReferenceLoader` in the `AsyncApiReaderSettings`.
135135
External references are always force converted to Json during resolution. This means that both yaml and json is supported - but not other serialization languages.
136136

137137
```csharp
138-
var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolution.ResolveAllReferences };
138+
var settings = new AsyncApiReaderSettings { ReferenceResolution = ReferenceResolutionSetting.ResolveAllReferences };
139139
var document = new AsyncApiStringReader(settings).Read(json, out var diagnostics);
140140
```
141141

@@ -155,7 +155,6 @@ var asyncApiDocument = new AsyncApiStringReader(settings).Read(yaml, out var dia
155155

156156
* [OpenAPI.Net](https://github.com/microsoft/OpenAPI.NET) - [MIT License](https://github.com/microsoft/OpenAPI.NET/blob/vnext/LICENSE)
157157
* [YamlDotNet](https://github.com/aaubry/YamlDotNet) - [MIT License](https://github.com/aaubry/YamlDotNet/blob/master/LICENSE.txt)
158-
* [JsonPointer.Net](https://github.com/json-everything/json-everything) - [MIT License](https://github.com/json-everything/json-everything/blob/master/LICENSE)
159158

160159
## Contribution
161160

‎website/docs/guides/references.mdx‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ The V2 server fields are `url` and `protocol`; the host is not serialized as `ht
6161

6262
Internal references are resolved by default during document reading. This covers component references such as `#/components/servers/shared` and document locations outside components, such as `#/servers/production`. The reference object remains a typed proxy: its target properties can be read directly, while `Reference.Reference` retains the URI and `UnresolvedReference` reports whether the target was found.
6363

64+
A local reference is a JSON pointer ([RFC 6901](https://www.rfc-editor.org/rfc/rfc6901)) into the document, so it can point at any location, not only at components: for example `#/channels/userSignup/messages/signedUp` or `#/servers/production/variables/port`. A key that contains `/` or `~` must be escaped as `~1` or `~0`. A channel keyed `user/signedup` is referenced as `#/channels/user~1signedup`; the unescaped `#/channels/user/signedup` points at a `signedup` member inside a `user` channel instead, and does not resolve.
65+
66+
Because a reference is a URI, its pointer is also percent-decoded before it is evaluated. `#/servers/a%20b` and `#/servers/a b` both refer to a server keyed `a b`, and resolve to the same server. A key that contains `%` itself must encode it as `%25`: a server keyed `a%20b` is referenced as `#/servers/a%2520b`. References the library writes for such keys, for example when converting between versions, are percent-encoded the same way.
67+
6468
Reference resolution is part of reading a full document with `Read`; `ReadFragment<T>` parses a fragment but does not run the document reference-resolution pass. A missing or invalid internal target is reported in diagnostics and remains unresolved.
6569

6670
```csharp
@@ -96,6 +100,26 @@ if (production is AsyncApiServerReference serverReference)
96100

97101
Consumers can often read a resolved proxy as the referenced type without first copying or replacing it. Keep the reference-type and resolution checks when your code requires a concrete target. For an unresolved reference, proxy getters can return `null`; avoid relying on target properties until `UnresolvedReference` is false. Reader errors and warnings are returned in `diagnostics`.
98102

103+
## Write references
104+
105+
References are written as references, including ones whose target could not be resolved, so they are not lost. To write local references as their target instead, set `ReferenceInline` on the writer settings. External references are always written as references. With `InlineReferences`, a local reference that cannot be resolved throws an `AsyncApiWriterException`.
106+
107+
```csharp
108+
using System.IO;
109+
using ByteBard.AsyncAPI.Models;
110+
using ByteBard.AsyncAPI.Writers;
111+
112+
var settings = new AsyncApiWriterSettings
113+
{
114+
ReferenceInline = ReferenceInlineSetting.InlineReferences,
115+
};
116+
117+
using var stream = new MemoryStream();
118+
document.SerializeAsYaml(stream, AsyncApiVersion.AsyncApi3_0, settings);
119+
```
120+
121+
A reference to a location the written document does not contain is written inline, even without `InlineReferences`. This happens when converting between versions: a 2.x reference to `#/channels/userSignup/publish/message` has no equivalent location in 3.x, and a 3.x reference to `#/operations/sendUserSignup/traits/0` has none in 2.x. References that still resolve in the written document, such as references to components or servers, stay references. AsyncAPI 2.x cannot reference tags or external documentation, so those are written inline when writing 2.x. See [version conversion](./version-conversion) for the other differences.
122+
99123
## External references
100124

101125
`AsyncApiReaderSettings.ReferenceResolution` defaults to `ReferenceResolutionSetting.ResolveInternalReferences`: local references are resolved and external references remain unresolved placeholders. Set `ResolveAllReferences` to load external references too. The built-in loader supports absolute `file://`, `http://`, and `https://` URIs. Relative external URIs require a `BaseUri`; the built-in loader resolves them relative to that URI as file paths. Other URI schemes require a custom loader.

‎website/docs/guides/version-conversion.mdx‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ var outputV3 = document.SerializeAsJson(AsyncApiVersion.AsyncApi3_0);
2222

2323
`AsyncApi3_0` is the API's V3 selector; the current V3 writer emits `asyncapi: 3.1.0`.
2424

25-
The serializer maps between the different version shapes, but a round trip is not guaranteed to preserve every representation detail. To bridge the versions, references may be moved, rewritten, or represented differently while keeping the specification's behavior as close as possible. V3-only model information cannot survive a downgrade when V2 has no corresponding field. In particular, channel keys may be normalized as addresses; generated operation keys can use `anonymous-operation-{int}` when no operation ID is available; operation IDs are not retained through a V2 downgrade; and when a V2 shape supports only one message in a location, only the first message is selected. References may also be reflowed or resolved during conversion.
25+
The serializer maps between the different version shapes, but a round trip is not guaranteed to preserve every representation detail. To bridge the versions, references may be moved, rewritten, or represented differently while keeping the specification's behavior as close as possible. V3-only model information cannot survive a downgrade when V2 has no corresponding field. In particular, channel keys may be normalized as addresses; generated operation keys can use `anonymous-operation-{int}` when no operation ID is available; operation IDs are not retained through a V2 downgrade; and when a V2 shape supports only one message in a location, only the first message is selected. V2 has one `publish` and one `subscribe` operation per channel, so a downgrade drops further V3 operations with the same action on a channel. V2 messages only exist inside operations, so a V3 message that no operation uses is not written.
2626

2727
Review converted output when round-trip fidelity matters, especially around removed fields, operation identifiers, multiple messages, and references. See the [AsyncAPI migration guide](https://www.asyncapi.com/docs/migration/migrating-to-v3) for specification-level changes.
28+
29+
## References
30+
31+
References that resolve in the written version stay references, such as references to components (`#/components/messages/userSignedUp`) or servers (`#/servers/production`). A reference to a location that does not exist in the written version is written inline instead:
32+
33+
- V2 references into `publish` or `subscribe` operations, such as `#/channels/userSignup/publish/message`, have no V3 location.
34+
- V3 references into `#/operations/...` have no V2 location.
35+
- V3 writes component schemas as Multi Format Schema Objects, so a reference into a schema, such as `#/components/schemas/User/properties/id`, has no V3 location.
36+
- V2 writes message headers as their schema, and cannot reference tags or external documentation, so those are written inline in V2.
37+
38+
V2 keys channels by address, and the reader normalizes the address into the V3 channel key. A V2 reference to another channel, such as `#/channels/user~1signedup`, is read as `#/channels/usersignedup`, and is written back by address in V2.
39+
40+
See [references](./references#write-references) for how references are written in general.

0 commit comments

Comments
 (0)