vld-tonic 0.2.0

tonic gRPC integration for the vld validation library — validate protobuf messages and metadata
Documentation

Crates.io docs.rs License

vld-tonic

tonic gRPC integration for the vld validation library.

Validate protobuf messages and gRPC metadata using vld schemas.

Installation

[dependencies]
vld = "0.1"
vld-tonic = "0.1"
tonic = "0.12"
serde = { version = "1", features = ["derive"] }

Quick Start — Message Validation

Attach validation rules to protobuf message types with impl_validate!:

use serde::Serialize;
use tonic::{Request, Response, Status};

// Protobuf message (generated by prost with serde support)
#[derive(Clone, Serialize)]
pub struct CreateUserRequest {
    pub name: String,
    pub email: String,
    pub age: i32,
}

// Attach validation rules
vld_tonic::impl_validate!(CreateUserRequest {
    name  => vld::string().min(2).max(50),
    email => vld::string().email(),
    age   => vld::number().int().min(0).max(150),
});

// In your gRPC service handler:
async fn create_user(request: Request<CreateUserRequest>) -> Result<Response<()>, Status> {
    let msg = vld_tonic::validate(request)?;
    // msg.name, msg.email, msg.age are validated
    Ok(Response::new(()))
}

impl_validate! generates:

  • msg.validate()Result<(), VldError>
  • msg.is_valid()bool
  • VldMessage trait impl (used by vld_tonic::validate)

Schema-Based Validation

If you prefer a separate schema definition:

vld::schema! {
    struct UserSchema {
        name: String  => vld::string().min(2),
        email: String => vld::string().email(),
    }
}

async fn handler(request: Request<CreateUserRequest>) -> Result<Response<()>, Status> {
    let msg = vld_tonic::validate_with::<UserSchema, _>(request)?;
    Ok(Response::new(()))
}

Metadata Validation

Validate gRPC request metadata (headers):

vld::schema! {
    #[derive(Debug, Clone)]
    struct AuthMeta {
        authorization: String => vld::string().starts_with("Bearer "),
    }
}

async fn handler(request: Request<MyMessage>) -> Result<Response<()>, Status> {
    let auth: AuthMeta = vld_tonic::validate_metadata(&request)?;
    println!("token: {}", auth.authorization);
    Ok(Response::new(()))
}

Metadata keys are converted from kebab-case to snake_case (x-request-idx_request_id). Values are coerced ("42" → number, "true" → boolean).

Metadata Interceptor

Validate metadata automatically for all requests on a service:

use tonic::transport::Server;

Server::builder()
    .add_service(MyServiceServer::with_interceptor(
        my_service,
        vld_tonic::metadata_interceptor::<AuthMeta>,
    ))
    .serve(addr)
    .await?;

Retrieve the validated metadata in handlers:

async fn handler(request: Request<MyMessage>) -> Result<Response<()>, Status> {
    let auth = vld_tonic::validated_metadata::<AuthMeta, _>(&request)
        .ok_or_else(|| Status::internal("Missing auth extension"))?;
    Ok(Response::new(()))
}

API Reference

Function Description
validate(request) Validate message via VldMessage trait
validate_ref(&msg) Validate reference without consuming request
validate_with::<S, _>(request) Validate against a vld::schema! type
validate_with_ref::<S, _>(&msg) Schema-based validation on reference
validate_metadata(&request) Validate gRPC metadata
validated_metadata(&request) Retrieve validated metadata from extensions
metadata_interceptor::<T>(request) Interceptor for metadata validation
vld_status(&error) Convert VldErrorStatus::InvalidArgument
vld_status_with_code(&error, code) Convert with custom gRPC code
impl_validate!(Type { ... }) Attach rules + implement VldMessage

Nested Message Validation

For nested protobuf messages, use vld::object():

vld_tonic::impl_validate!(CreateUserRequest {
    name    => vld::string().min(2),
    address => vld::object()
        .field("city", vld::string().min(1))
        .field("zip",  vld::string().len(5)),
});

Running the Example

cargo run -p vld-tonic --example tonic_basic

License

MIT