Expand description
§tonic-rest-build
Build-time REST codegen from protobuf google.api.http annotations for Tonic + Axum.
Part of the tonic-rest ecosystem — define your API once in proto files, get gRPC, REST, and OpenAPI 3.1.
Reads a compiled proto FileDescriptorSet, extracts google.api.http annotations,
and generates Axum route handler code that calls through Tonic service traits — keeping
proto files as the single source of truth for both gRPC and REST APIs.
§Key Features
- Proto as single source of truth — one definition drives gRPC, REST endpoints, and OpenAPI docs
- Build-time codegen — Axum handlers generated from
FileDescriptorSetat compile time; zero runtime overhead or reflection - Standard annotations — uses
google.api.httpbindings, not a proprietary DSL - Zero-config auto-discovery — scans the descriptor set for any service with HTTP annotations; no manual package listing required
- SSE for server streaming — streaming RPCs are automatically exposed as Server-Sent Events endpoints
- Serde auto-wiring —
configure_prost_serdediscovers WKT fields and applies#[serde(with)]attributes automatically
§How It Works
Annotate your proto service with google.api.http:
service ItemService {
rpc CreateItem(CreateItemRequest) returns (Item) {
option (google.api.http) = { post: "/v1/items" body: "*" };
}
rpc GetItem(GetItemRequest) returns (Item) {
option (google.api.http) = { get: "/v1/items/{item_id}" };
}
}The generated code calls through Tonic service traits — sharing auth, validation, and business logic with gRPC handlers:
async fn rest_create_item<S: ItemService>(
State(service): State<Arc<S>>,
headers: HeaderMap,
Json(body): Json<CreateItemRequest>,
) -> Result<Json<Item>, RestError> {
let req = build_tonic_request::<_, ()>(body, &headers, None);
let response = service.create_item(req).await?;
Ok(Json(response.into_inner()))
}§Quick Start
[dependencies]
tonic-rest = "0.1"
[build-dependencies]
tonic-rest-build = "0.1"
prost-build = "0.14"Zero-config build.rs — auto-discovers packages from the descriptor set:
use tonic_rest_build::{RestCodegenConfig, generate, dump_file_descriptor_set};
const PROTO_FILES: &[&str] = &["proto/service.proto"];
const PROTO_INCLUDES: &[&str] = &["proto"];
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let descriptor_path = format!("{out_dir}/file_descriptor_set.bin");
// Phase 1: Compile protos → descriptor set
let descriptor_bytes = dump_file_descriptor_set(PROTO_FILES, PROTO_INCLUDES, &descriptor_path);
// Phase 2: Compile protos → Rust (prost/tonic)
let mut config = prost_build::Config::new();
config.file_descriptor_set_path(&descriptor_path);
config.compile_protos(PROTO_FILES, PROTO_INCLUDES).unwrap();
// Phase 3: Generate REST routes
let rest_config = RestCodegenConfig::new();
let code = generate(&descriptor_bytes, &rest_config).unwrap();
std::fs::write(format!("{out_dir}/rest_routes.rs"), code).unwrap();
}§Configuration
Explicit package mapping (e.g., when using pub use v1::*; re-exports):
let config = RestCodegenConfig::new()
.package("auth.v1", "auth")
.package("users.v1", "users")
.wrapper_type("crate::core::Uuid")
.extension_type("my_app::AuthInfo")
.public_methods(&["Login", "SignUp"])
.sse_keep_alive_secs(30);§RestCodegenConfig Options
| Method | Default | Description |
|---|---|---|
.package(proto, rust) | auto-discover | Proto package → Rust module mapping |
.extension_type(path) | None | Extension type for Axum Extension<T> extraction |
.public_methods(list) | empty | Methods whose paths skip auth middleware |
.wrapper_type(path) | None | Rust type for single-field wrapper messages (UUID) |
.proto_root(path) | "crate" | Root module for proto types |
.runtime_crate(path) | "tonic_rest" | Path to runtime types |
.sse_keep_alive_secs(n) | 15 | SSE keep-alive interval |
.extra_forwarded_headers(&[..]) | empty | Extra HTTP headers to forward to gRPC metadata |
§Feature Flags
| Feature | Default | Description |
|---|---|---|
helpers | on | dump_file_descriptor_set and configure_prost_serde helpers (adds prost-build dep) |
§Serde Attribute Helper
Auto-discover proto fields and apply #[serde(with)] attributes:
use tonic_rest_build::configure_prost_serde;
configure_prost_serde(
&mut config,
&descriptor_bytes,
PROTO_FILES,
"crate::serde_wkt",
&[(".google.protobuf.Timestamp", "opt_timestamp")],
&[(".my.v1.UserRole", "user_role")],
);§Runtime Dependencies
The generated handler code references types from these crates — ensure they are
in your [dependencies]:
| Crate | Used for |
|---|---|
tonic-rest | RestError, build_tonic_request, sse_error_event |
tonic | tonic::Status, tonic::Request, service traits |
axum | Router, extractors, Json, Query, SSE |
futures | Stream, StreamExt (streaming endpoints only) |
serde_json | Json extractor/response |
§Generated Code
For each service with HTTP annotations:
{service}_rest_router(service: Arc<S>) -> Router— route registration- Per-method handler functions with proper extractors
PUBLIC_REST_PATHS: &[&str]— paths that bypass authentication middlewareall_rest_routes(...)— combined router for all services
§Handler Variants
| HTTP Method | Body | Response |
|---|---|---|
| POST/PUT/PATCH | Json<T> | Json<Response> |
| GET | Query<T> | Json<Response> |
| DELETE | T::default() | StatusCode::NO_CONTENT |
| GET (streaming) | Query<T> | Sse<impl Stream> |
§Planned
additional_bindings: ProtoHttpRule.additional_bindings(multiple REST mappings per gRPC method) is not supported. Only the primary HTTP binding is processed.- Partial body selectors: Only
body: "*"(full body) andbody: ""(no body) are supported. Thebody: "field_name"partial body binding from the gRPC-HTTP transcoding spec is not implemented. - Repeated WKT fields:
configure_prost_serdedoes not wire serde adapters for lists of well-known types (e.g.repeated google.protobuf.Timestamp). Single fields of these types work correctly.
For a complete end-to-end example with proto files, build.rs, REST handlers, and OpenAPI generation,
see auth-service-rs.
§Companion Crates
| Crate | Purpose | Cargo section |
|---|---|---|
| tonic-rest-core | Shared descriptor types | internal |
| tonic-rest | Runtime types | [dependencies] |
| tonic-rest-build (this) | Build-time codegen | [build-dependencies] |
| tonic-rest-openapi | OpenAPI 3.1 generation | CLI / CI |
§Compatibility
| tonic-rest-build | prost / prost-build | tonic | axum | MSRV |
|---|---|---|---|---|
| 0.1.x | 0.14 | 0.14 | 0.8 | 1.85 |
§License
MIT OR Apache-2.0
§API Reference
Structs§
- Prost
Serde Config - Builder for configuring prost serde attributes.
- Rest
Codegen Config - Configuration for REST route code generation.
Enums§
- Generate
Error - Error returned by
generate.
Functions§
- configure_
prost_ serde - Configure prost serde attributes by scanning a
FileDescriptorSet. - configure_
prost_ serde_ with_ options - Configure prost serde attributes with custom
rename_allstrategy. - dump_
file_ descriptor_ set - Invoke
protocto produce a binaryFileDescriptorSet. - generate
- Generate REST route code from a compiled proto file descriptor set.
- try_
configure_ prost_ serde - Fallible version of
configure_prost_serde. - try_
configure_ prost_ serde_ with_ options - Fallible version of
configure_prost_serde_with_options. - try_
dump_ file_ descriptor_ set - Fallible version of
dump_file_descriptor_set.