Skip to content

Validation

Oxide’s validators wrap the validator crate and expose its rules through a #[request] attribute. A request struct describes the expected payload, and the framework returns a 422 JSON envelope on failure without invoking the handler.

Declare the request

use oxide_http::request;
#[request]
pub struct StoreTodoRequest {
#[validate(required, length(min = 1, max = 200))]
pub title: Option<String>,
#[validate]
pub done: Option<bool>,
}
  • Every field is Option<T> so the validator can distinguish “missing” from “present but invalid”.
  • #[validate(required)] is your “not null” rule.
  • #[validate(length / email / range / regex / ...)] — any validator combinator works.

Using it in a handler

Take the request struct as the handler’s argument. The framework runs validation before calling you.

pub async fn store(form: StoreTodoRequest) -> Response {
// Getting here means validation passed. Required fields are Some.
let title = form.title.expect("validated as Some");
// ...
Response::text(format!("stored {title}"))
}

Error envelope

A failing request returns 422 with:

{
"message": "ข้อมูลไม่ถูกต้อง",
"errors": [
{
"ref": "title",
"type": "required",
"desc": ["ต้องระบุ title"]
}
]
}

Every offending field surfaces — not just the first.

Query-string parse

request.query::<T>() parses the URL query into T using the same envelope shape on failure:

#[derive(serde::Deserialize)]
pub struct PageQuery {
#[serde(default = "default_page")]
pub page: u32,
}
pub async fn list(req: Request) -> Response {
let query: PageQuery = match req.query() {
Ok(q) => q,
Err(resp) => return resp,
};
// ...
Response::text(format!("page {}", query.page))
}