connectrpc / connectrpc/connect-rust
codegen: the RPC kind is re-derived at six sites in four orderings, and only one is exhaustive
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 509
- Forks
- 66
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 10
Description
Summary
codegen.rs re-derives the RPC kind from the raw client_streaming / server_streaming booleans at six independent sites, in four different branch orderings, with unary as an implicit else at five of them. Only one site is exhaustive.
// repeated at six sites
let client_streaming = m.client_streaming.unwrap_or(false);
let server_streaming = m.server_streaming.unwrap_or(false);
| Site | Order of arms |
|---|---|
| route registration | server, client, bidi, unary |
lookup arms |
bidi, client, server, unary |
call_* arms |
bidi, client, server, unary |
| trait method | server, client, bidi, unary |
| client method | client, bidi, server, unary |
| spec consts | exhaustive match (cs, ss) |
Riding along with it, the same per-method type resolution is redone 19 times — method.input_type.as_deref().unwrap_or("") followed by a resolver.rust_type(..) call, for a value that is invariant per method — and make_field_ident(&name.to_snake_case()) is recomputed at five sites.
Why it matters
This is the structural driver behind the file's churn. codegen.rs is the highest-commit file in the repository, and 44% of its commits are "a runtime API changed shape and the emitted call sites had to follow". Each of those fans out to four to six near-identical sites because there is no per-method value object to change once.
The codebase already knows the branches can drift. There is a test whose comment says it exists for no other reason:
// The #75 fix substitutes `#input_arg` at four interpolation
// sites in `generate_trait_method` (server-streaming, client-
// streaming, bidi, unary). This drives all four shapes through
// a colliding cross-package input to catch any regression that
// accidentally drops the substitution from one branch.
A test that exists because the structure cannot guarantee four branches stay in sync is the structure asking to be changed.
Proposed direction
Build a MethodCtx once per method, fallibly, before any emitter runs — proto name, snake ident, spec const ident, an RpcKind enum, idempotency, and the resolved owned/view input and output types. Every emitter then takes &MethodCtx and does an exhaustive match ctx.kind, so a reordering or a missed arm is a compile error rather than a silent gap.
Note the runtime already has the right enum: connectrpc::spec::StreamType has exactly these four variants. Codegen only ever emits it as a token and never uses it as a Rust-side discriminant.
This also subsumes a smaller problem. The route-registration builder collects into Vec<TokenStream> with .unwrap() on resolver calls, while its two immediate neighbours use .collect::<Result<Vec<_>>>()? — nine unwrap/expect calls in non-test code, with a comment acknowledging that threading Result through is a follow-up. Under MethodCtx the resolution happens once, fallibly, and the emitters never touch the resolver.
Size
Roughly +110 for the enum, struct and builder, −150 across the six prologues and ladders. Net around −40, spread over six functions. The value is not the line count; it is that the recurring edit becomes a one- or two-site change.
Related
A second-order duplication worth folding in if this is done: the router registration and the monomorphic FooServer<T> dispatcher emit character-identical call-and-encode tails for each of the four kinds, differing only in the decode preamble. Extracting the shared tail turns the eight branches into four plus a helper.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in codegen.rs, especially generate_trait_method and the six RPC-kind sites; read the existing cross-package input test described in the issue. Trace the resolver calls and route, lookup, call, trait, client, and spec emitters before designing MethodCtx. Done means per-method values are resolved once, emitters use exhaustive RpcKind matching, and the relevant tests pass without non-test resolver unwraps.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design, tooling
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 44/100