feat(proto): generate generic GRPC API implementations#1950
feat(proto): generate generic GRPC API implementations#1950
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the crates/proto build-time code generation to also emit generic, per-RPC-method server-side traits and blanket tonic server implementations, aiming to standardize gRPC server implementations across all services referenced in PR #1742.
Changes:
- Update
crates/proto/build.rsto generate additional server facade modules from protobuf descriptors (plus regeneratemod.rsfiles). - Add build-time dependencies needed for code generation and descriptor introspection (
codegen,prost-types). - Run
rustfmtover generated Rust sources at build time.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| crates/proto/build.rs | Adds server module generation from descriptors, new mod.rs generation logic, and build-time rustfmt pass. |
| crates/proto/Cargo.toml | Adds build dependencies (codegen, prost-types) needed by build.rs. |
| Cargo.toml | Pins prost-types to align with pinned prost versions in the workspace. |
| Cargo.lock | Locks new dependency resolutions for codegen and prost-types. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn rustfmt_generated(dir: &Path) -> miette::Result<()> { | ||
| let mut rs_files = Vec::new(); | ||
| collect_rs_files(dir, &mut rs_files)?; | ||
|
|
||
| if rs_files.is_empty() { | ||
| return Ok(()); | ||
| } | ||
|
|
||
| let status = Command::new("rustfmt") | ||
| .args(&rs_files) | ||
| .status() | ||
| .into_diagnostic() | ||
| .wrap_err("running rustfmt on generated files")?; | ||
|
|
||
| if !status.success() { | ||
| miette::bail!("rustfmt failed with status: {status}"); | ||
| } |
There was a problem hiding this comment.
rustfmt_generated unconditionally invokes the rustfmt binary during the build script. This adds a non-trivial build-time cost and makes builds depend on an external tool being available/working for the host toolchain. Consider gating formatting behind an env var (e.g. only in CI or when explicitly enabled), or gracefully skipping formatting when rustfmt isn't found, so consumers can still build the crate.
There was a problem hiding this comment.
@kkovaacs This is maybe a good idea (skipping if not found).
| for fds in descriptor_sets { | ||
| for file in &fds.file { | ||
| let package = file.package.as_deref().unwrap_or_default(); | ||
| let package = package.replace('.', "_"); | ||
|
|
||
| for service in &file.service { | ||
| let service_name = service.name.as_deref().unwrap_or("Service"); | ||
| let service_name = to_snake_case(service_name); | ||
| let module_name = format!("{}_{}", &package, service_name); | ||
|
|
||
| let contents = | ||
| Service::from_descriptor(service, &package).generate().scope().to_string(); | ||
|
|
||
| let path = dst_dir.join(format!("{module_name}.rs")); | ||
| fs::write(path, contents).into_diagnostic().wrap_err("writing server module")?; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
generate_server_modules iterates every FileDescriptorSet and every file within each set. Because each descriptor set includes transitive imports, the same service (e.g. rpc.Api) will appear in multiple sets and the corresponding {module_name}.rs will be regenerated/overwritten multiple times. Consider tracking generated (package, service) pairs in a HashSet and skipping duplicates to reduce build time and avoid accidental divergence if descriptor compilation ever differs.
|
|
||
| modules.push(format!("pub mod {module};")); | ||
| } | ||
|
|
There was a problem hiding this comment.
generate_mod_rs builds the module list from read_dir without sorting. Directory iteration order is filesystem-dependent, so the generated mod.rs can change nondeterministically across machines/runs, causing unnecessary rebuilds and flaky diffs in generated output. Collect module names, sort them, then write them in a stable order.
| modules.sort(); |
There was a problem hiding this comment.
@kkovaacs This should be taken care of by rustfmt step, but in the case this isn't found, maybe clippy will still complain, making this worth doing?
This PR adds the build-time generation of generic GRPC API implementations for all services discussed in PR #1742.
For each method we're generating a trait like this:
The idea is that the actual implementation should consist of implementing all per-method traits for the API server (most of the time just
decode/encode/handle), and then the provided generic implementation for the API can be used:In this form this PR should be a no-op, because the none of our API implementations have been moved to this new model. That will be done in multiple follow-up PRs.