Support parameterized AsyncAPI consumer channels - #914
Closed
Levyks wants to merge 1 commit into
Closed
Conversation
mwadams
reviewed
Aug 5, 2026
| foreach (ChannelParameter parameter in op.Parameters) | ||
| { | ||
| string parameterName = ToCamelCase(parameter.Name); | ||
| w.WriteLine($"channel = channel.Replace(\"{{{EscapeString(parameter.Name)}}}\", {parameterName}, StringComparison.Ordinal);"); |
Contributor
There was a problem hiding this comment.
I will switch this out for an alloc free version.
Contributor
|
Thanks very much for this PR - excellent work. I am going to sweep it into a PR to produce V5.3 which also cherry picks a number of OpenApi and AsyncApi changes from our Arazzo workflow work. When I do that, I will make a change to rework the dynamic channel building code into a zero-allocation form. |
This was referenced Aug 5, 2026
mwadams
added a commit
that referenced
this pull request
Aug 5, 2026
* Pass binding context to generated AsyncAPI consumers * Support parameterized AsyncAPI consumer channels * Give a document its workspace, and let a builder hand one over (#803) The generation work that follows needs two things the document primitives did not offer. A document has to be able to say which workspace created it, so a caller can hand a value to something that owns a lifetime rather than guessing whether one is shared. And a builder has to be able to hand its rows to another document without a serialize-and-reparse round trip. JsonDocumentBuilder now implements IWorkspaceCreatedDocument, and the document interface gains the two operations that make the handover possible: reading a local element's contiguous UTF-8 without materialising it, and appending a local element's rows into another metadata database rebased to their new location. Both are additive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Generate OpenAPI clients and servers that describe themselves (#803) Four changes to what the OpenAPI generators emit. A generated client can now take a context-threaded request body. The server result factory has long offered Ok<TContext>(Source<TContext>, workspace), so a caller assembles a body lazily with its context threaded through and it materialises in one pass with no per-item closure. A client had no counterpart, so anyone with a collection to put in a REQUEST body had to close over it. The machinery was already there: the generators take the set of body pointers whose type is an object or array, and emit the generic overload only for those. The server command computed that set and the client command never did, so the client path silently opted out under what its own doc comment called the conservative default. OpenAPI 2.0 was worse and is worth naming: the parameter did not exist there at all, so a 2.0 SERVER was also missing the closure-free response factories every 3.x server has had. A binary response now carries its body through the result factory, which is a breaking change. The old parameterless Ok() could not express a body at all; the shipped example recipe said so in a comment and returned Ok() anyway. It now takes the bytes or a writer, and the content type the handler chooses. An optional request body is optional. A body not marked required generated a mandatory parameter, so a caller had to supply something for a body the specification says may be absent. Descriptions in the source document become XML doc comments on the generated members, escaped so a description containing markup does not break the build. The schema classifier takes the document root alongside the schema so it can follow a reference rather than classifying the reference itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Give AsyncAPI a responder, and its request a workspace (#803) Request/reply had only one half. A caller could send a request and await the correlated reply, but nothing in the transport surface let a service BE the responder: subscribe to a channel, handle each request, and publish the reply on the correlated reply channel. SubscribeReplyAsync is that half. It ships with a default implementation that throws NotSupportedException, so a transport that does not support responders is unaffected and an existing custom transport still compiles. RequestAsync now takes the JsonWorkspace that owns the reply's lifetime, which is a breaking change: it is a required parameter ahead of the optional headers and cancellation token, so every call site and every custom implementation of the abstract overload needs it. The reply was previously materialised against a lifetime the caller could not control, which is the wrong shape for a caller folding the reply into a document it owns. The generated methods for a parameterised channel address now take a span or UTF-8 memory as well as a string, so an address composed from bytes does not have to become a string on the way to the transport. The producer's byte overload rents a pooled buffer, because the send outlives the call that started it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Add Corvus.Text.Json.OpenApi.Polly (#803) AsyncAPI has had a Polly package since its transports did: a resilience pipeline wrapped around the transport, so a deployment configures retry and circuit-breaking without every call site knowing about it. OpenAPI had the same need and no counterpart, so a caller wanting a resilient transport wrapped IApiTransport by hand. ResilientApiTransport is that counterpart, decorating an IApiTransport with a Polly pipeline and passing every operation through unchanged otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Regenerate the example recipes against the updated generators (#803) Mechanical, with one exception. The recipes carry committed generator output, so a generator change leaves them stale until they are regenerated. The exception is the advanced-server recipe, whose download handler returned the parameterless Ok() a binary response used to generate, with a comment saying the streaming was handled elsewhere and a discarded local standing in for the photo it could not send. The factory now takes the body, so the recipe sends the photo and its content type, which is what it was describing all along. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Document the release, and cut it as 5.3.0 (#803) The prose that goes with the preceding commits: the AsyncAPI guide gains the responder and the workspace-carrying request, the OpenAPI guide gains the context-threaded request body, and two new documents cover consuming generated types and the performance techniques the generators now rely on. The agent guidance moves with it. Three new skills (context threading, the bytes-to-bytes discipline, typed model construction) and two updated ones describe the conventions these generators emit against, which is exactly the guidance a contributor needs to extend them without reintroducing the closures and round trips this work removed. 5.3.0 rather than 5.2.14 because two changes are breaking: RequestAsync takes a workspace, and a binary response carries its body through the result factory. This repository has shipped breaking changes in a patch before (5.2.7 renamed a generated member for schemas with a property called "create"), but that one reached almost nobody and only on regeneration. These reach every caller of AsyncAPI request/reply and every handler returning a binary response. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Regenerate the recipes, and credit the contributions (#803) The AsyncAPI consumer changes reach the committed recipe output, and the streetlights recipes show why the parameterised-channel contribution matters: they subscribed to smartylighting.streetlights.1.0.action.{streetlightId}.lighting.measured literally, placeholder and all, so the generated consumer listened on a channel no publisher ever wrote to. They now take the streetlight id and compose the address from it. VERSIONHISTORY credits both contributions to Levy Barbosa, and records that the allocation-free composition was added when #914 was merged rather than being part of what was contributed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Update the AsyncAPI runtime tests for parameterised channels (#803) The parameterised-channel work broke twenty-one call sites in the AsyncAPI runtime tests, and that is the feature rather than a regression. Those tests were asserting the bug: they delivered to smartylighting.streetlights.1.0.action.{streetlightId}.lighting.measured with the placeholder still in it, because that is the address the generated consumer really subscribed to. They now pass a streetlight id and deliver to the address it composes. The release notes move that change from New features to Breaking changes. It is both, but the half a consumer has to act on is the compile break: StartAsync() compiled before and does not now. Twenty-one call sites in one test project is a fair preview of what a downstream consumer meets. They also record that this reaches AsyncAPI 2.6, not only 3.0. The 2.6 generator holds a 3.0 generator and delegates emission to it, so both contributions applied to 2.6 the moment they applied to 3.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq * Document the capabilities this release adds (#803) Three gaps, found by asking what a reader would have to reverse-engineer from the release notes. docs/OpenApi.md had not been touched at all, so the headline client feature and a breaking change were both undocumented. It now covers building a request body without closures (the client counterpart of the server's Ok<TContext>), that an optional body is optional, and that a binary response carries its body through the result factory. docs/AsyncApi.md documented channel parameters for producers only, which was correct until now: consumers ignored them and subscribed to the template literally. The section now covers the consumer side, including the span overload, and says plainly what the old behaviour was, since anyone upgrading meets the compile break first and deserves to know it was hiding a bug. Corvus.Text.Json.OpenApi.Polly shipped with no documentation whatsoever. It now has the section its AsyncAPI counterpart has had, with the caveat that a retry pipeline cannot tell an idempotent operation from one that is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq --------- Co-authored-by: Levy Barbosa <marcio.levy20@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
mwadams
added a commit
that referenced
this pull request
Aug 5, 2026
Main now carries the OpenAPI and AsyncAPI generation work that was lifted off this branch and released as 5.3.0, together with Levy Barbosa's two contributions merged on top of it. This brings it back, so the campaign continues on the released shape rather than on the shape it was ported from. Where both sides had touched the same file, main wins on the generators: its AsyncApi30CodeGenerator, IMessageTransport, and the advanced-server recipe are this branch's own work plus #913, #914, and the allocation-free composition added when they were merged. The fifty-one lines only this branch had were all the forms main replaced. This branch keeps what is its own: the Arazzo projects in the solution, and the buffer-and-pooling skill's continuation-token example, which names an Arazzo type that exists here and did not exist in the release. The AsyncAPI guide takes main's new consumer documentation and restores the sentence pointing at the Arazzo request/reply step. Derived artefacts were regenerated rather than resolved: both sides had regenerated the recipes and the sample catalog from different generator states, so picking a side would have committed one of them stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wwPnkvEn24dgt5T8mHGJq
mwadams
added a commit
that referenced
this pull request
Aug 5, 2026
…920) * Fix AsyncAPI recipe call sites broken by the 5.3.0 parameterised-channel change The 5.3.0 release regenerated the ExampleRecipes Generated folders with the parameterised-channel breaking change from #914, but left recipes 037, 038, and 039 calling the removed parameterless StartAsync() overload, so ExampleRecipes.slnx did not build. The call sites now supply the channel parameter, the simulated broker publishes target the concrete channel the consumer subscribes to, and the README samples match the code again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157Tdszg6wzgWeKgGYRBNKV * Fix #917 and #918: escape link descriptions and align generated doc comments with signatures A response links entry's description was the last place specification text reached a generated doc comment verbatim; a line break in it terminated the comment and the remainder parsed as source code, the failure mode #916 reported for operation summaries. The 3.0, 3.1, and 3.2 client generators now route it through CodeEmitHelpers.EscapeXml (#917). Generated doc comments also contradicted the signatures they document, so a consuming project with GenerateDocumentationFile and TreatWarningsAsErrors failed its build on CS1572/CS1573. The model templates' constructor, conversion-operator, From<T>, and TryParseValue param tags now match, and the OpenAPI client method docs cover the trailing validationMode and responseValidationMode parameters in all four generators (#918). Covspec link tests pin the flattened, escaped emission; a new GeneratedDocumentationTests suite runs the CLI and compiles its output under DocumentationMode.Diagnose, gating errors and the doc-mismatch warning family. All new tests failed against the unfixed generators with exactly the defect diagnostics before passing with the fixes. Example recipes are regenerated; the diff is doc-comment lines and lock metadata. CS1591 is tracked separately in #919. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157Tdszg6wzgWeKgGYRBNKV * Regenerate benchmark C/ directories and repair the AsyncAPI benchmarks for 5.3.0 The model templates changed in the #918 fix, so every C/ directory is regenerated; B/ is untouched. Modified-file diffs are doc-comment lines only. The sweep also carries 5.3.0-era nested type-name truncation renames on the large schemas (Ui5, Krakend, GeoJson, CmakePresets, and friends), because 5.3.0 changed the truncation without regenerating C/; git pairs those as delete plus add. Corvus.Text.Json.AsyncApi.Benchmarks was already broken on main: its hand-written BenchmarkTransport still implemented the pre-5.3.0 RequestAsync signature without the JsonWorkspace parameter, and its checked-in Generated folder predated 5.3.0. The transport gains the workspace parameter (unused by design; the reply is pre-parsed at setup), the Generated folder is regenerated with the current asyncapi-generate, and the harness follows the models into the .Models subnamespace. The benchmarks solution builds with no warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157Tdszg6wzgWeKgGYRBNKV * Gate the V4 engine's generated documentation alongside V5 The V4 engine was inspected for the same defect classes and verified clean: its doc param tags name parameters that exist in the V4 signatures, and its AppendParagraphs emits description text per line, HTML-encoded, so a multi-line or markup-carrying description cannot escape the doc comment. GeneratedDocumentationTests now runs the same five fixture schemas through --engine V4 and compiles the output under DocumentationMode.Diagnose, so both engines stay guarded rather than only the one that was broken. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157Tdszg6wzgWeKgGYRBNKV --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
StartAsyncparameters for templated AsyncAPI channel addresses.For example, a channel address such as
orders.{orderId}.creatednow generates:Testing
dotnet test tests/Corvus.Text.Json.AsyncApi.CodeGeneration.Tests/Corvus.Text.Json.AsyncApi.CodeGeneration.Tests.csproj --no-restore --framework net10.0