Skip to content

Attraqt/tonic-rest

 
 

Repository files navigation

tonic-rest

Crates.io docs.rs License MSRV

Define your API once in proto files — get gRPC, REST, and OpenAPI 3.1.

                    ┌──────────────────┐
                    │   .proto files   │
                    │ google.api.http  │
                    └────────┬─────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
      ┌──────────────┐ ┌──────────┐ ┌─────────────┐
      │  Tonic gRPC  │ │ Axum REST│ │ OpenAPI 3.1 │
      │   handlers   │ │ handlers │ │    spec     │
      └──────────────┘ └──────────┘ └─────────────┘

tonic-rest reads standard google.api.http proto annotations and generates type-safe Axum REST handlers alongside your existing Tonic gRPC services — all at build time, with zero runtime reflection.

Key Features

  • Proto as single source of truth — one definition drives gRPC, REST endpoints, and OpenAPI docs
  • Build-time codegen — Axum handlers are generated from FileDescriptorSet at compile time; no 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 — server-streaming RPCs are automatically exposed as Server-Sent Events endpoints
  • OpenAPI 3.1 pipeline — 12-phase transform pipeline produces a clean spec with security, validation constraints, and proper SSE annotations
  • Google error model — gRPC errors map to structured JSON responses following the Google API error model
  • Serde adapters — ready-made #[serde(with)] modules for Timestamp, Duration, FieldMask, and proto3 enums

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}" };
  }
  rpc DeleteItem(DeleteItemRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = { delete: "/v1/items/{item_id}" };
  }
}

tonic-rest-build generates type-safe Axum handlers at compile time:

pub fn item_service_rest_router<S>(service: Arc<S>) -> Router
where
    S: ItemService + Send + Sync + 'static,
{
    Router::new()
        .route("/v1/items", axum::routing::post(rest_create_item::<S>))
        .route("/v1/items/{item_id}", axum::routing::get(rest_get_item::<S>))
        .route("/v1/items/{item_id}", axum::routing::delete(rest_delete_item::<S>))
        .with_state(service)
}

Each handler transcodes HTTP/JSON to proto and calls through Tonic service traits, sharing auth, validation, and business logic with gRPC handlers.

Crates

Crate Purpose Cargo section
tonic-rest-core Shared protobuf descriptor types (internal) internal
tonic-rest Runtime types (error mapping, request bridging, SSE, serde adapters) [dependencies]
tonic-rest-build Build-time codegen (proto → Axum handlers) [build-dependencies]
tonic-rest-openapi OpenAPI 3.1 spec generation and patching (library + CLI) CLI / CI

Quick Start

[dependencies]
tonic-rest = "0.1"

[build-dependencies]
tonic-rest-build = "0.1"

Internal Release / Git Tag Versioning

This repository is commonly consumed directly from Git tags inside the organization.

Create a release tag:

git tag tonic-rest@0.1.5
git push origin tonic-rest@0.1.5

Then consume crates from another project:

[dependencies]
tonic-rest = { git = "ssh://git@github.com/Attraqt/tonic-rest.git", tag = "tonic-rest@0.1.5" }

[build-dependencies]
tonic-rest-build = { git = "ssh://git@github.com/Attraqt/tonic-rest.git", tag = "tonic-rest@0.1.5" }

This ensures reproducible builds pinned to a released version.

build.rs

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");

    // Compile protos → descriptor set
    let descriptor_bytes = dump_file_descriptor_set(PROTO_FILES, PROTO_INCLUDES, &descriptor_path);

    // Compile protos → Rust (prost/tonic) as usual
    let mut config = prost_build::Config::new();
    config.file_descriptor_set_path(&descriptor_path);
    config.compile_protos(PROTO_FILES, PROTO_INCLUDES).unwrap();

    // Generate REST routes — auto-discovers services with HTTP annotations
    let rest_config = RestCodegenConfig::new();
    let code = generate(&descriptor_bytes, &rest_config).unwrap();
    std::fs::write(format!("{out_dir}/rest_routes.rs"), code).unwrap();
}

OpenAPI Generation (CLI)

cargo install tonic-rest-openapi --features cli

tonic-rest-openapi generate --config api/openapi/config.yaml --cargo-toml Cargo.toml

Example Project

For a complete end-to-end example with proto files, build.rs, REST handlers, and OpenAPI generation, see auth-service-rs.

Compatibility

tonic-rest tonic axum prost MSRV
0.1.x 0.14 0.8 0.14 1.85

License

MIT OR Apache-2.0

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Rust 99.8%
  • Makefile 0.2%