Web frameworks
ntex 3 and ntex 4 extractors that validate JSON bodies, query strings, path parameters, forms, headers and cookies with vld schemas.

Drop-in replacements for ntex::web::types::{Json, Query, Path, 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-ntex = "0.5"                                   # ntex 4
ntex = { version = "4", features = ["tokio"] }

For ntex 3:

Cargo.toml
[dependencies]
vld-ntex = { version = "0.5", default-features = false, features = ["ntex-3"] }
ntex = { version = "3", features = ["tokio"] }

Features

FeatureDefaultDescription
ntex-4yesBuild against ntex 4
ntex-3noBuild against ntex 3 (disable default features)
compressnoDecode Content-Encoding: gzip / deflate / br request bodies (enables ntex's compress)

Enable exactly one of ntex-3 / ntex-4; both or neither is a compile error. The selected crate is re-exported as vld_ntex::ntex.

MSRV: 1.97 with ntex-4 (the crate's declared rust-version); ntex-3 builds on 1.88 with --ignore-rust-version. See MSRV.

Example

main.rs
use ntex::web::{self, App, HttpResponse, HttpServer};
use vld_ntex::{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),
    }
}

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!("search: {}", q.q))
}

#[ntex::main]
async fn main() -> std::io::Result<()> {
    let app = || {
        App::new()
            .route("/users", web::post().to(create_user))
            .route("/users/{id}", web::get().to(get_user))
            .route("/search", web::get().to(search))
    };

    // ntex 4
    HttpServer::new(async move |_| app())
        .bind("127.0.0.1:8080", ntex::SharedCfg::default())?
        .run()
        .await
    // ntex 3: HttpServer::new(async move || app()).bind("127.0.0.1:8080")?.run().await
}

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

API

ItemDescription
VldJson<T>JSON body (requires a JSON Content-Type)
VldQuery<T>Query string
VldPath<T>Path parameters (decoded by ntex's router)
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
VldNtexErrorError of every extractor (WebResponseError): status(), body(), error()
VldBodyLimitBody size limit for VldJson / VldForm, registered as app state
ntexRe-export of the selected ntex crate
preludeEverything above plus vld::prelude::*

Body limit

VldJson and VldForm read the payload themselves up to 2 MiB; ntex's JsonConfig / PayloadConfig do not apply. Register VldBodyLimit as app state:

use vld_ntex::VldBodyLimit;

// ntex 4
App::new().with_config(web::WebAppConfig::new().set_state(VldBodyLimit(8 * 1024 * 1024)));
// ntex 3
App::new().state(VldBodyLimit(8 * 1024 * 1024));

Gotchas

  • Without the compress feature compressed request bodies are not decoded, so a gzip body fails as malformed JSON (422). With it, the limit applies to the decoded size.
  • ntex 4 needs a recent compiler (1.97). Pin ntex-3 if your toolchain is older.
  • The ntex version of vld-ntex and your own ntex dependency must match, or the extractors will not implement your version's FromRequest.