g2h 0.1.1

A code generator that bridges gRPC services to HTTP/JSON endpoints using Axum
Documentation
# Detailed Usage Guide

This guide explains how to use `g2h` to expose your gRPC services as HTTP endpoints.

## 1. Define your gRPC service in a `.proto` file

Create a standard protobuf definition for your service:

```protobuf
syntax = "proto3";
package user.v1;

service UserService {
    rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
    rpc GetUser(GetUserRequest) returns (GetUserResponse);
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
}

message CreateUserResponse {
    string id = 1;
    bool success = 2;
}

message GetUserRequest {
    string id = 1;
}

message GetUserResponse {
    string id = 1;
    string name = 2;
    string email = 3;
}
```

## 2. Set up your build script (`build.rs`)

```rust
use std::env;
use std::path::PathBuf;
use g2h::BridgeGenerator;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    
    // Simple approach with default settings
    BridgeGenerator::with_tonic_build()
        .build_prost_config()
        .compile_protos(&["proto/user_service.proto"], &["proto"])?;
    
    // Alternative approach with more control
    let tonic_generator = tonic_build::configure().service_generator();
    let bridge_generator = BridgeGenerator::new(tonic_generator);
    
    prost_build::Config::new()
        .service_generator(Box::new(bridge_generator))
        .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
        .file_descriptor_set_path(out_dir.join("user_service_descriptor.bin"))
        .compile_protos(&["proto/user_service.proto"], &["proto"])?;
    
    Ok(())
}
```

## 3. Include the generated code in your project

```rust
// In your lib.rs or main.rs
include!(concat!(env!("OUT_DIR"), "/user.v1.rs"));
```

## 4. Implement your gRPC service

```rust
use tonic::{Request, Response, Status};
use user_service::user_service_server::{UserService, UserServiceServer};
use user_service::{CreateUserRequest, CreateUserResponse, GetUserRequest, GetUserResponse};

// Include the generated code
pub mod user_service {
    include!(concat!(env!("OUT_DIR"), "/user.v1.rs"));
}

// Your service implementation
#[derive(Default, Clone)]
pub struct MyUserService {}

#[tonic::async_trait]
impl UserService for MyUserService {
    async fn create_user(
        &self,
        request: Request<CreateUserRequest>,
    ) -> Result<Response<CreateUserResponse>, Status> {
        let req = request.into_inner();
        
        // Your implementation here
        let response = CreateUserResponse {
            id: "user-123".to_string(),
            success: true,
        };
        
        Ok(Response::new(response))
    }
    
    async fn get_user(
        &self,
        request: Request<GetUserRequest>,
    ) -> Result<Response<GetUserResponse>, Status> {
        let req = request.into_inner();
        
        // Your implementation here
        let response = GetUserResponse {
            id: req.id,
            name: "John Doe".to_string(),
            email: "john.doe@example.com".to_string(),
        };
        
        Ok(Response::new(response))
    }
}
```

## 5. Set up your Axum application using the generated router

```rust
use axum::{Router, Server};
use std::net::SocketAddr;
use user_service::user_service_server::{UserServiceServer};
use user_service::user_service_handler;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create your service implementation
    let user_service = MyUserService::default();
    
    // Create the HTTP router using g2h's generated handler
    let http_router = user_service_handler(user_service);
    
    // Set up your Axum application
    let app = Router::new()
        .nest("/api", http_router);
        
    // Start the server
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("Listening on {}", addr);
    Server::bind(&addr)
        .serve(app.into_make_service())
        .await?;
        
    Ok(())
}
```

## 6. Access your API

Your service is now accessible through both gRPC and HTTP:

**gRPC (using a gRPC client)**
```
service: user.v1.UserService
method: CreateUser
payload: { "name": "Jane Doe", "email": "jane.doe@example.com" }
```

**HTTP**
```http
POST /api/user.v1.UserService/CreateUser
Content-Type: application/json

{
  "name": "Jane Doe",
  "email": "jane.doe@example.com"
}
```

## Advanced Configurations

### Custom Path Prefixes

If you want to customize the route paths, you can use Axum's routing mechanisms:

```rust
let http_router = user_service_handler(user_service);

// Add a version prefix
let app = Router::new()
    .nest("/api/v1", http_router);
```

### Combining Multiple Services

You can combine multiple service handlers into a single Axum router:

```rust
let user_service = MyUserService::default();
let auth_service = MyAuthService::default();

let user_router = user_service_handler(user_service);
let auth_router = auth_service_handler(auth_service);

let app = Router::new()
    .nest("/api/users", user_router)
    .nest("/api/auth", auth_router);
```

### Working with Metadata

The generated handlers preserve metadata between HTTP headers and gRPC metadata:

```rust
async fn create_user(
    &self,
    request: Request<CreateUserRequest>,
) -> Result<Response<CreateUserResponse>, Status> {
    // Access HTTP headers as metadata
    let auth_token = request.metadata().get("authorization")
        .ok_or_else(|| Status::unauthenticated("Missing authorization"))?;
        
    // Your implementation...
    
    // Add metadata to the response
    let mut response = Response::new(create_user_response);
    response.metadata_mut().insert("x-request-id", "12345".parse().unwrap());
    
    Ok(response)
}
```