Shared crates

vld-http-common

Framework-independent HTTP helpers shared by all vld web adapters — value coercion, query/form/header/cookie parsing, content-type checks and the error body.

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"
FeatureDescription
conformanceTest 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

ItemDescription
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

ItemDescription
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

ItemDescription
VldHttpErrorStatus + 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, ErrorWithMessageThe body shapes as vld::schema! structs (usable as OpenAPI response schemas)
UNSUPPORTED_MEDIA_TYPE_MESSAGE, UNSUPPORTED_FORM_MEDIA_TYPE_MESSAGEThe 415 messages

Limits

ItemDescription
DEFAULT_BODY_LIMIT2 MiB, the default body limit of every adapter
MAX_FORM_FIELDS1000
VldBodyLimit(usize)Limit set as app/request data in vld-actix, vld-ntex, vld-poem (vld-rama has its own type)

Gotchas

  • path in the error body is spelled out by this crate (.user.name, .items[0]) rather than taken from PathSegment's Display, so the wire format stays stable.
  • extract_path_param_names was removed in 0.5.