Errors

Error model

VldError, ValidationIssue, IssueCode and PathSegment.

Every failed parse returns a VldError. It is a list of issues; parsing continues after the first problem, so you get all of them at once.

pub struct VldError {
    pub issues: Vec<ValidationIssue>,
}

pub struct ValidationIssue {
    pub code: IssueCode,               // what went wrong, with parameters
    pub message: String,               // human-readable text
    pub path: Vec<PathSegment>,        // where: [Field("items"), Index(0), Field("sku")]
    pub received: Option<serde_json::Value>, // the offending value, when known
}

pub enum PathSegment {
    Field(String), // displayed as ".name"
    Index(usize),  // displayed as "[0]"
}

All of them implement Serialize and Deserialize, so you can return them from an API or store them. VldError implements Display and std::error::Error.

Reading issues

use vld::prelude::*;

let schema = vld::array(vld::object().field("sku", vld::string().len(8))).min_len(1);
let err = schema.parse(r#"[{"sku": "short"}]"#).unwrap_err();

let issue = &err.issues[0];
assert_eq!(issue.code.key(), "invalid_length");
assert_eq!(issue.path_string(), "[0].sku"); // `issue.path` holds the segments

match &issue.code {
    IssueCode::InvalidLength { expected, .. } => assert_eq!(*expected, 8),
    _ => unreachable!(),
}
  • issue.path_string() joins the path (.user.tags[0], empty for the root).
  • issue.code.key() is a stable snake_case string (too_small, invalid_string, …). Prefer it over matching messages.
  • IssueCode is #[non_exhaustive]: add a _ => arm when you match on it.
  • issue.code.params() returns the template parameters (minimum, expected, …) as strings.

The full list of codes is on Issue codes.

Display

Display prints one line per issue: path: message, plus , received <value> when the value is known (except for invalid_type, whose message already names the received type).

.name: String must be at least 2 characters, received "A"
.email: Invalid email address, received "nope"
.address.zip: Required field is missing

For other layouts see Formatting.

Missing vs null

An absent required key reports missing_field ("Required field is missing"). A key that is present with null reports invalid_type (expected string, received null). Optional, nullable and defaulted fields accept both.

Building errors yourself

Use VldError in custom checks and when combining validations:

let mut errors = VldError::new();
errors
    .issue(IssueCode::Custom { code: "passwords_differ".into() })
    .message("Passwords do not match")
    .path_field("confirm")
    .finish();
errors.push(IssueCode::Custom { code: "weak".into() }, "Password is too weak");

assert_eq!(errors.issues.len(), 2);
assert_eq!(errors.issues[0].code.key(), "passwords_differ");
MethodPurpose
VldError::new() / single(code, msg) / from_code(code)create
push(code, msg) / push_code(code)add an issue (push_code uses the default message)
issue(code)…finish()builder with message, path_field, path_index, received
merge(other)append another error's issues
with_prefix(segment)prepend a path segment to every issue
is_empty()no issues

IssueCode::Custom { code } is reserved for your own codes. Built-in checks always use the typed variants.