pub fn generate(request: &CodeGeneratorRequest) -> Result<CodeGeneratorResponse>Expand description
Generate a CodeGeneratorResponse from a protoc CodeGeneratorRequest.
This is the entry point for the protoc plugin (protoc-gen-connect-rust).
It parses the comma-separated request.parameter into Options and
delegates to generate_services — service stubs only. Callers must
run protoc-gen-buffa (or equivalent) separately for message types.
§Output
Per proto with at least one service: a <stem>.__connect.rs content
file with the service stubs. Under encodable_impls=all_messages,
also per proto whose messages yield at least one Encodable impl
pair. Per package with at least one such proto:
a <pkg>.mod.rs stitcher that include!s the content files. The
stitcher filename intentionally matches protoc-gen-buffa’s, so run
this plugin into a separate output directory and use
protoc-gen-buffa-packaging to wire both trees, as shown in this
repo’s buf.gen.yaml examples.
Under file_per_package the per-proto split is collapsed: one
<dotted.pkg>.rs per package with all service stubs inlined, no
per-proto content files, and no stitcher. Drop the
protoc-gen-buffa-packaging invocations from your buf.gen.yaml
under this layout — there are no per-file content files or
stitchers for it to wire, and leaving it in produces dead mod.rs
output without an error. Either let your downstream build tool
synthesise the module tree from <dotted.package>.rs filenames (BSR
cargo SDKs do this automatically) or hand-write the mod.rs. See
generate_services.
A worked file_per_package buf.gen.yaml:
version: v2
plugins:
- local: protoc-gen-buffa
out: src/gen/buffa
opt: [file_per_package]
- local: protoc-gen-connect-rust
out: src/gen/connect
opt: [file_per_package, buffa_module=crate::gen::buffa]You then mount each tree with a hand-written mod.rs (or let BSR’s
cargo SDK pipeline do it):
pub mod buffa { /* one `pub mod <pkg> { include!("<pkg>.rs"); }` per package */ }
pub mod connect { /* same, pointing at src/gen/connect */ }§Recognized options
buffa_module=<rust_path>— where you mounted the buffa-generated module tree (e.g.buffa_module=crate::proto). Shorthand forextern_path=.=<rust_path>. This is the option most local users want.extern_path=<proto>=<rust>— map a specific proto package prefix to a Rust module path. Repeatable; longest-prefix-match wins.extern_path=.=<path>is the catch-all (equivalent tobuffa_module). At least one catch-all mapping is required so every type resolves. Every mapped path must point at buffa-generated code from buffa ≥ 0.8.0 with views enabled — the stubs use thebuffa::HasMessageViewimpls and owned-view wrappers generated with each message (buffa-types0.8+ qualifies for the well-known types).file_per_package— emit one<dotted.pkg>.rsper proto package instead of the per-proto split + stitcher. Setprotoc-gen-buffa’s ownfile_per_packageoption to the same value — the BSR/toniclib.rssynthesis assumes both plugins use the same filename convention; mismatched settings produce a valid but asymmetric layout you would have to wire by hand. Keep using a dedicated output directory (the documented split-path setup already does this) — the filename matchesprotoc-gen-buffa’s and would silently overwrite in a shared one. SeeCodeGenConfig::file_per_packagefor thestrategy: directoryconstraint.strict_utf8_mapping— seeCodeGenConfig::strict_utf8_mapping.no_json— disableserdederives on generated message types, for proto-only builds. Pair it withconnectrpc’sdefault-features = false(thejsoncargo feature off) so the runtime drops its matching serde bounds. Ignored in this plugin (no message types emitted); accepted for compatibility with the unified path.no_register_fn— suppress the per-fileregister_types(&mut TypeRegistry)aggregator. SeeCodeGenConfig::emit_register_fn. Ignored in this plugin (no message types emitted); accepted for compatibility with the unified path.gate_client_feature— prefix every emittedFooClient<T>struct and itsimplblock with#[cfg(feature = "client")].gate_client_feature=<name>— same gate, but use<name>as the Cargo feature instead ofclient.encodable_impls=all_messages— emit the::connectrpc::Encodableview impl pair for every message defined in each targeted proto, not only for RPC output types, and emit a companion file even for protos that declare no services. Use this in the generation run of a crate that owns message types consumed by other crates’ services (a shared proto crate in a multi-crate split): Rust’s orphan rules require these impls to live in the crate that defines the view types, so downstream service crates skip them and their handlers would otherwise have to return owned messages orPreEncoded::from_view. (View bodies serve the proto codec only — JSON-codec requests getUnimplemented, as for any view body.) The emitting crate must depend onconnectrpc, and the companion files for service-less packages must be mounted like any other plugin output — an unmounted companion surfaces as a missingEncodableimpl in the consuming crate. Messages mapped to a foreign crate viaextern_pathare still skipped.encodable_impls=outputsis the explicit default. SeeOptions::encodable_impls.
§Client-side cfg gate
When gate_client_feature is set, the consumer crate must declare
the named Cargo feature (client by default). Without it, the generated
FooClient items will be absent from the crate namespace.
Two consumer patterns:
-
Dep-forwarding (
client = ["connectrpc/client"], withconnectrpc = { ..., features = ["server"] }and no"client"in that dep’s feature list): turns the gate into a real server-only escape hatch. Disabling the feature dropsconnectrpc/client(and its transport stack) from the dependency graph entirely. This is the intended use; seeconnectrpc-healthfor the minimal example. -
Marker (
client = [], no forwarding): satisfies the gate without slimming the dependency graph. Use only when you want the cfg infrastructure in place but aren’t ready to gate the dep yet.