Web frameworks
Actix-web extractors that validate JSON bodies, query strings, path parameters, forms, headers and cookies with vld schemas.

Drop-in replacements for web::Json, web::Query, web::Path and web::Form, plus header and cookie extractors. Shared behaviour (status codes, error body, coercion, header/cookie rules) is in the web adapters overview.

Install

Cargo.toml
[dependencies]
vld = "0.5"
vld-actix = "0.5"
actix-web = "4"

No features. Works with actix-web 4.4+. MSRV 1.85 (see MSRV).

Example

main.rs
use actix_web::{web, App, HttpResponse, HttpServer};
use vld_actix::{VldBodyLimit, VldJson, VldPath, VldQuery};

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

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

vld::schema! {
    #[derive(Debug)]
    pub struct Search {
        pub q: String => vld::string().min(1),
        pub page: Option<i64> => vld::number().int().min(1).optional(),
    }
}

async fn create_user(user: VldJson<CreateUser>) -> HttpResponse {
    HttpResponse::Created().body(format!("created {}", user.name))
}

async fn get_user(path: VldPath<UserPath>) -> HttpResponse {
    HttpResponse::Ok().body(format!("user #{}", path.id))
}

async fn search(q: VldQuery<Search>) -> HttpResponse {
    HttpResponse::Ok().body(format!("{} (page {})", q.q, q.page.unwrap_or(1)))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .app_data(VldBodyLimit(8 * 1024 * 1024)) // optional, default 2 MiB
            .route("/users", web::post().to(create_user))
            .route("/users/{id}", web::get().to(get_user))
            .route("/search", web::get().to(search))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

The full example is crates/vld-actix/examples/actix_basic.rs (cargo run -p vld-actix --example actix_basic).

API

ItemDescription
VldJson<T>JSON body (requires a JSON Content-Type)
VldQuery<T>Query string
VldPath<T>Path parameters, including custom patterns such as {id:\d+}
VldForm<T>application/x-www-form-urlencoded body
VldHeaders<T>Request headers (X-Request-Id → x_request_id)
VldCookie<T>Cookies from all Cookie headers
VldActixErrorError of every extractor (ResponseError): status(), body(), error()
VldBodyLimitBody size limit for VldJson / VldForm, registered with app_data (re-exported from vld-http-common)
preludeEverything above plus vld::prelude::*

Extractors implement Deref / DerefMut to T, so user.name works directly; user.0 or let VldJson(user) = user; moves the value out.

Gotchas

  • Body limit: VldJson and VldForm read the payload themselves; actix's JsonConfig and PayloadConfig do not apply. Register VldBodyLimit(n) with App::app_data (or on a scope / resource); not wrapped in web::Data.
  • Compressed bodies (Content-Encoding: gzip, br, ...) are decoded before validation; the limit applies to the decoded size.
  • Actix keeps %2F, %25 and %2B encoded in match_info; VldPath decodes them, so /files/a%2Fb gives "a/b".