connectrpc / connectrpc/connect-rust

codegen: the RPC kind is re-derived at six sites in four orderings, and only one is exhaustive

Open
#252 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.