Shared crates
The common core of vld-axum, vld-actix, vld-ntex, vld-poem, vld-rama, vld-rocket, vld-salvo, vld-tower, vld-warp and vld-tonic. It is framework-agnostic: no HTTP framework or http crate dependency, only vld, serde and serde_json.
You normally get it through an adapter. Depend on it directly to write an adapter for another framework, or to build the same error body in your own handlers. The rules it implements are described in the web adapters overview.
Install
Cargo.toml
[dependencies]
vld-http-common = "0.5"
| Feature | Description |
|---|---|
conformance | Test support: the shared adapter conformance suite (conformance module, conformance_suite!). Used by the adapters' dev-dependencies |
MSRV 1.85 (see MSRV).
Example
A minimal adapter for a JSON body:
use vld_http_common::{parse_json_body, require_json_content_type, VldHttpError};
vld::schema! {
#[derive(Debug)]
pub struct User {
pub name: String => vld::string().min(2),
}
}
fn extract(content_type: Option<&str>, body: &[u8]) -> Result<User, VldHttpError> {
require_json_content_type(content_type)?; // 415
let value = parse_json_body(body)?; // 422 on malformed JSON
Ok(User::parse_value(&value)?) // 422 on validation errors
}
let err = extract(Some("application/json"), br#"{"name": "A"}"#).unwrap_err();
assert_eq!(err.status(), 422);
assert_eq!(err.body()["issues"][0]["code"], "too_small");
let err = extract(Some("text/plain"), b"{}").unwrap_err();
assert_eq!(err.status(), 415);
Text sources go through parse_coerced instead of parse_value:
use vld_http_common::{parse_coerced, query_string_to_json};
vld::schema! {
#[derive(Debug)]
pub struct Search {
pub q: String => vld::string(),
pub page: i64 => vld::number().int().min(1),
pub tags: Vec<String> => vld::array(vld::string()),
}
}
let value = query_string_to_json("q=123&page=2&tags=a");
let s: Search = parse_coerced(&value).unwrap();
assert_eq!((s.q.as_str(), s.page, s.tags.len()), ("123", 2, 1));
API
Parsing
| Item | Description |
|---|---|
coerce_value(raw) | Text → JSON without losing information ("42" → 42, "01234" stays a string) |
parse_query_string(q) / query_string_to_json(q) | Query string or form body → JSON object; repeated keys and a[] become arrays |
parse_form_body(bytes) | Form body → JSON; 422 on non-UTF-8 or more than MAX_FORM_FIELDS fields |
parse_json_body(bytes) | JSON body → Value; 422 on malformed JSON |
headers_to_json(pairs) | Headers → JSON (snake_case names, names with _ skipped, repeats joined with ", ") |
cookies_to_json(header) / cookie_headers_to_json(values) | One / all Cookie header values → JSON (first occurrence wins) |
path_params_to_json(pairs) | Decoded path parameters → JSON (coerced) |
url_decode(s) / percent_decode(s) | Form decoding (+ → space) / percent decoding (+ kept) |
parse_coerced::<T>(value) | Re-export of vld::schema::parse_coerced: validate with the string / array fallback |
Content types
| Item | Description |
|---|---|
is_json_content_type(ct) | application/json or application/<x>+json, any case, parameters ignored |
require_json_content_type(ct) / require_form_content_type(ct) | Ok(()) or a 415 VldHttpError (also for a missing header) |
Errors
| Item | Description |
|---|---|
VldHttpError | Status + VldError: validation / parse (422), payload_too_large (413), unsupported_media_type / unsupported_form_media_type (415); status(), body(), error(), into_error() |
format_vld_error(err) / format_issues(err) | The 422 body / its issues list |
format_payload_too_large(), format_unsupported_media_type(), format_generic_error(msg) | The 413 / 415 / {"error": msg} bodies |
ValidationErrorBody, ValidationIssue, ErrorBody, ErrorWithMessage | The body shapes as vld::schema! structs (usable as OpenAPI response schemas) |
UNSUPPORTED_MEDIA_TYPE_MESSAGE, UNSUPPORTED_FORM_MEDIA_TYPE_MESSAGE | The 415 messages |
Limits
| Item | Description |
|---|---|
DEFAULT_BODY_LIMIT | 2 MiB, the default body limit of every adapter |
MAX_FORM_FIELDS | 1000 |
VldBodyLimit(usize) | Limit set as app/request data in vld-actix, vld-ntex, vld-poem (vld-rama has its own type) |
Gotchas
pathin the error body is spelled out by this crate (.user.name,.items[0]) rather than taken fromPathSegment'sDisplay, so the wire format stays stable.extract_path_param_nameswas removed in 0.5.