vld-tonic 0.3.0

tonic gRPC integration for the vld validation library — validate protobuf messages and metadata
Documentation
[![Crates.io](https://img.shields.io/crates/v/vld-tonic?style=for-the-badge)](https://crates.io/crates/vld-tonic)
[![docs.rs](https://img.shields.io/docsrs/vld-tonic?style=for-the-badge)](https://docs.rs/vld-tonic)
[![License](https://img.shields.io/badge/license-MIT-green?style=for-the-badge)](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

| 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 `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