[](https://crates.io/crates/vld-tonic)
[](https://docs.rs/vld-tonic)
[](https://github.com/s00d/vld/blob/main/LICENSE)
# vld-tonic
[tonic](https://crates.io/crates/tonic) gRPC integration for the
[vld](https://crates.io/crates/vld) validation library.
Validate protobuf messages and gRPC metadata using `vld` schemas.
## Installation
```toml
[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!`:
```rust
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:
```rust
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):
```rust
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-id` → `x_request_id`). Values are coerced (`"42"` → number,
`"true"` → boolean).
## Metadata Interceptor
Validate metadata automatically for all requests on a service:
```rust
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:
```rust
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
| `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 `VldError` → `Status::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()`:
```rust
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
```bash
cargo run -p vld-tonic --example tonic_basic
```
## License
MIT