tonic-rest-build 0.1.5

Build-time REST codegen from protobuf google.api.http annotations for Tonic + Axum
Documentation

tonic-rest-build

Crates.io docs.rs License MSRV

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 FileDescriptorSet at compile time; zero runtime overhead or reflection
  • Standard annotations — uses google.api.http bindings, 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-wiringconfigure_prost_serde discovers 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 middleware
  • all_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: Proto HttpRule.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) and body: "" (no body) are supported. The body: "field_name" partial body binding from the gRPC-HTTP transcoding spec is not implemented.
  • Repeated WKT fields: configure_prost_serde does 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