vld-warp 0.2.0

Warp integration for the vld validation library
Documentation

Crates.io docs.rs License Platform GitHub issues GitHub stars

vld-warp

Warp integration for the vld validation library.

Features

Filter / Function Source Description
vld_json::<T>() JSON body Validates JSON request body
vld_query::<T>() Query string Validates URL query parameters
vld_form::<T>() Form body Validates URL-encoded form body
vld_param::<T>(name) Single path segment Validates one URL path segment
vld_path::<T>(names) Path tail Validates all remaining path segments
validate_path_params::<T>(pairs) Pre-extracted params Validates mixed static/dynamic path segments
vld_headers::<T>() HTTP headers Validates request headers
vld_cookie::<T>() Cookie header Validates cookie values
handle_rejection Converts vld rejections into JSON responses

Validation failures are returned as 422 Unprocessable Entity with a JSON error body.

Installation

[dependencies]
vld-warp = "0.1"
vld = "0.1"
warp = "0.3"
serde_json = "1"

Quick Start

use vld_warp::prelude::*;
use warp::Filter;

vld::schema! {
    #[derive(Debug, Clone)]
    pub struct CreateUser {
        pub name: String  => vld::string().min(2).max(50),
        pub email: String => vld::string().email(),
    }
}

#[tokio::main]
async fn main() {
    let route = warp::post()
        .and(warp::path("users"))
        .and(warp::path::end())
        .and(vld_json::<CreateUser>())
        .map(|u: CreateUser| {
            warp::reply::json(&serde_json::json!({"name": u.name}))
        })
        .recover(handle_rejection);

    warp::serve(route).run(([0, 0, 0, 0], 3030)).await;
}

Path Parameters

Warp doesn't have a built-in named path parameter extractor. vld-warp provides three approaches to fill this gap.

vld_param — single segment

Extracts one path segment and validates it. The name argument becomes the JSON key so it matches your schema's field name.

vld::schema! {
    #[derive(Debug, Clone)]
    pub struct UserId {
        pub id: i64 => vld::number().int().min(1),
    }
}

// GET /users/<id>
let route = warp::path("users")
    .and(vld_param::<UserId>("id"))
    .and(warp::path::end())
    .map(|p: UserId| warp::reply::json(&serde_json::json!({"id": p.id})));

vld_path — all remaining segments (tail)

Consumes all remaining path segments via warp::path::tail() and maps them 1-to-1 to the provided names. The segment count must match the name count exactly (otherwise → 404).

Best for routes where all remaining segments are dynamic.

vld::schema! {
    #[derive(Debug, Clone)]
    pub struct PostPath {
        pub user_id: i64 => vld::number().int().min(1),
        pub post_id: i64 => vld::number().int().min(1),
    }
}

// GET /posts/<user_id>/<post_id>
let route = warp::path("posts")
    .and(vld_path::<PostPath>(&["user_id", "post_id"]))
    .map(|p: PostPath| {
        warp::reply::json(&serde_json::json!({
            "user_id": p.user_id,
            "post_id": p.post_id
        }))
    });

validate_path_params — mixed static / dynamic

For complex routes with interleaved static and dynamic segments, extract each String segment with warp::path::param::<String>() yourself, then call validate_path_params in and_then to validate them all at once.

vld::schema! {
    #[derive(Debug, Clone)]
    pub struct CommentPath {
        pub user_id: i64    => vld::number().int().min(1),
        pub post_id: i64    => vld::number().int().min(1),
        pub comment_id: i64 => vld::number().int().min(1),
    }
}

// GET /users/<uid>/posts/<pid>/comments/<cid>
let route = warp::path("users")
    .and(warp::path::param::<String>())
    .and(warp::path("posts"))
    .and(warp::path::param::<String>())
    .and(warp::path("comments"))
    .and(warp::path::param::<String>())
    .and(warp::path::end())
    .and_then(|uid: String, pid: String, cid: String| async move {
        validate_path_params::<CommentPath>(&[
            ("user_id", &uid),
            ("post_id", &pid),
            ("comment_id", &cid),
        ])
    });

Recovery Handler

Always add .recover(handle_rejection) to convert vld rejections into structured JSON:

let routes = create.or(search).recover(handle_rejection);

Error response format:

{
  "error": "Validation failed",
  "issues": [
    { "path": ".name", "message": "String must be at least 2 characters" }
  ]
}

Running Examples

cargo run -p vld-warp --example warp_basic

Example Requests

# Create user (valid)
curl -X POST http://localhost:3030/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alice","email":"alice@example.com","age":30}'

# Create user (invalid — triggers 422)
curl -X POST http://localhost:3030/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"A","email":"bad","age":-1}'

# Get user by id (vld_param)
curl http://localhost:3030/users/42

# Invalid user id (vld_param — triggers 422)
curl http://localhost:3030/users/0

# Get post (vld_path — tail params)
curl http://localhost:3030/posts/7/99

# Get comment (validate_path_params — mixed segments)
curl http://localhost:3030/users/1/posts/2/comments/3

# Search (query params)
curl "http://localhost:3030/search?q=hello&page=1&limit=10"

# Health check
curl http://localhost:3030/health

License

MIT