Schemas

Other types

from_serde, from_str, bytes, decimal, IP networks, socket addresses, paths, files, durations, raw JSON and nested schemas.

from_serde and from_str

Bridge any existing Rust type into a schema.

ConstructorReadsError
vld::from_serde::<T>()the JSON value through T: Deserialize (Rust enums, uuid::Uuid, url::Url, …)parse_error with serde's message
vld::from_str::<T>()a JSON string through T: FromStr (IpAddr, numbers in strings, …)invalid_format (from_str) with the error's Display
use vld::prelude::*;
use std::net::IpAddr;

#[derive(Debug, PartialEq, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
enum Status { Active, OnHold }

let status = vld::from_serde::<Status>()
    .schema(serde_json::json!({"type": "string", "enum": ["active", "on_hold"]}));
assert_eq!(status.parse(r#""on_hold""#).unwrap(), Status::OnHold);

let ip = vld::from_str::<IpAddr>().parse(r#""::1""#).unwrap();
assert!(ip.is_loopback());

The JSON Schema is {} for from_serde and {"type": "string"} for from_str; set your own with .schema(json).

Bytes

vld::bytes() returns Vec<u8>. By default it reads a JSON array of integers 0..=255.

MethodEffect
base64()also accept a padded Base64 string
base64url()also accept a Base64URL string (+ and / rejected)
hex()also accept a hex string (optional 0x prefix)
min_len / max_len / len / non_empty()size of the decoded bytes
let key = vld::bytes().base64().len(4);
assert_eq!(key.parse(r#""AAECAw==""#).unwrap(), vec![0, 1, 2, 3]);

Decimal

Feature decimal. vld::decimal() returns rust_decimal::Decimal from a JSON string or number (plain or scientific notation). Bounds take strings so they keep full precision:

// features = ["decimal"]
let amount = vld::decimal().non_negative().max("1000000").multiple_of("0.01");
assert!(amount.parse(r#""19.99""#).is_ok());

Methods: min, max, gt, lt, multiple_of, positive, negative, non_negative, non_positive.

Network types

ConstructorOutputMethods
vld::ip_network() (feature net)ipnet::IpNet from a CIDR stringipv4_only(), ipv6_only()
vld::socket_addr()std::net::SocketAddr from ipv4:port or [ipv6]:port (no hostnames)min_port(p), max_port(p)

For IP addresses as plain strings use string().ip(); for IpAddr use from_str::<IpAddr>().

Duration

vld::duration() returns std::time::Duration. It accepts a non-negative number of seconds (3, 1.5) or a string: "10s", "1.5s", "250ms" (integer milliseconds), ISO 8601 "PT15S" / "PT1.5S".

use std::time::Duration;

let timeout = vld::duration().max_secs(60);
assert_eq!(timeout.parse(r#""250ms""#).unwrap(), Duration::from_millis(250));
assert_eq!(timeout.parse("1.5").unwrap(), Duration::from_millis(1500));

Bounds: min(Duration), max(Duration), min_secs(u64), max_secs(u64).

Raw JSON

vld::json_value() returns a serde_json::Value with shape constraints: object(), array(), require_key("id"), max_depth(n) (json_too_deep when nesting is deeper). Use vld::any() to accept anything without checks.

Path

vld::path() returns std::path::PathBuf.

MethodCheck
absolute() / relative()path form
exists() / file() / dir()checked against the server's filesystem
within(base)the path must stay inside base; returns base.join(input)
Paths from requests are untrusted. Without within(base), any absolute path or .. is accepted, and exists() / file() / dir() tell a client whether arbitrary paths exist on your machine. within resolves ., .. and existing symlinks before the check, so "../etc/passwd" and "/base/../etc/x" are rejected even if they do not exist. Read the security guide.
let upload = vld::path().relative().within("/srv/uploads");
let p = upload.parse(r#""avatars/ada.png""#).unwrap();
assert_eq!(p, std::path::PathBuf::from("/srv/uploads/avatars/ada.png"));
assert!(upload.parse(r#""../etc/passwd""#).is_err()); // path_outside_base

File

Feature file (and file-advanced for hashes, image size and EXIF). vld::file() reads a file path (a string, or an object with a string path) and returns a ValidatedFile with path(), size(), media_type(), extension(), bytes(), read_bytes() and open().

MethodCheck
min_size(bytes) / max_size(bytes) / non_empty()file size; reads stop after max_size + 1 bytes
extension("png") / extensions(&[...]) / deny_extension(..)file extension
media_type("image/*") / media_types(&[...]) / deny_media_type(..)MIME type sniffed from the content (infer; wildcards like image/* allowed)
allow_magic_type("png") / deny_magic_type(..)file kind sniffed from the content, by extension name
sha256(hex) / md5(hex)checksum (file-advanced)
min_width / max_width / min_height / max_heightimage dimensions (file-advanced)
require_exif()image has EXIF data (file-advanced)
within(base)confine the path, as for vld::path()
store_in_memory() (default) / store_path_only()keep the bytes in the output or only the path
vld::file() opens and reads the file named by the input. Always add within(base) when the path comes from a client.

Nested schemas

vld::nested(T::parse_value) uses another schema! / derive type as a field schema. vld::nested!(T) does the same and also registers T as a named OpenAPI component, so the parent's JSON Schema contains a $ref. See OpenAPI.