Other types
from_serde and from_str
Bridge any existing Rust type into a schema.
| Constructor | Reads | Error |
|---|---|---|
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.
| Method | Effect |
|---|---|
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
| Constructor | Output | Methods |
|---|---|---|
vld::ip_network() (feature net) | ipnet::IpNet from a CIDR string | ipv4_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.
| Method | Check |
|---|---|
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) |
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().
| Method | Check |
|---|---|
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_height | image 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.