Getting started

Introduction

What vld is, what problem it solves and how it compares to Zod and serde.

vld is a runtime validation library for Rust, modelled on Zod. You describe the shape of your data with schema values, then parse untrusted input with them. A successful parse returns typed Rust values; a failed one returns a VldError listing every problem with its path.

use vld::prelude::*;

let schema = vld::string().trim().min(2).max(50);

assert_eq!(schema.parse(r#""  Alex ""#).unwrap(), "Alex");

let err = schema.parse(r#""A""#).unwrap_err();
assert_eq!(err.issues[0].code.key(), "too_small");

Why not just serde?

serde turns JSON into Rust types and stops at the first error. It knows nothing about lengths, formats, ranges or cross-field rules, and its errors are strings meant for developers.

vld sits on top of serde_json::Value:

  • Constraints live next to the type: vld::string().email(), vld::u16().min(1), vld::array(...).max_len(10).
  • All issues at once, each with a path (items[2].email), a typed IssueCode and a message you can translate.
  • Typed output: schema! structs and #[derive(Validate)] give you plain Rust structs.
  • One source of truth for docs: every schema can emit JSON Schema / OpenAPI 3.1.
  • Integrations for web frameworks, databases, queues, config, CLI and UI crates reuse the same schemas and error format.

Coming from Zod

Most Zod concepts map one to one.

Zodvld
z.string().min(2).email()vld::string().min(2).email()
z.number().int().positive()vld::number().int().positive() or vld::u32().positive()
z.object({...})vld::schema! { struct ... }, #[derive(Validate)] or vld::object()
z.array(z.string()).max(5)vld::array(vld::string()).max_len(5)
z.enum(["a", "b"])vld::enumeration(&["a", "b"])
z.union([...])vld::union!(a, b, c) / a.or(b)
z.discriminatedUnion("type", ...)vld::discriminated_union("type").variant(...)
.optional() / .nullable() / .nullish()same names
.default(x).with_default(x)
.catch(x).catch(x)
.refine() / .superRefine().refine() / .super_refine()
.transform() / .pipe() / z.preprocess().transform() / .pipe() / vld::preprocess()
z.lazy()vld::lazy()
z.coerce.number()vld::number().coerce()
error.flatten() / z.treeifyError() / z.prettifyError()flatten_error() / treeify_error() / prettify_error()
zod-to-json-schemabuilt in: JsonSchema::json_schema() (feature openapi)

Differences worth knowing:

  • Sizes are named after what they count: strings use min / max (characters), arrays min_len / max_len, sets and files min_size / max_size, objects and records min_keys / max_keys.
  • Integers are their own schemas (vld::i32(), vld::u64()…) and are read exactly, without an f64 round trip.
  • Unknown object keys are stripped by default, as in Zod; use .strict() on vld::object() to reject them.
  • Unions return Either<A, B> / Either3<A, B, C> because Rust has no anonymous union types.

Crates

The core crate vld has no required dependencies besides serde and serde_json; heavier validators sit behind features. Around it are integration crates:

  • Web: vld-axum, vld-actix, vld-ntex, vld-poem, vld-rama, vld-rocket, vld-salvo, vld-tower, vld-warp, vld-tonic. See the web overview.
  • OpenAPI and JSON Schema: vld-utoipa, vld-schemars, vld-aide.
  • Codegen and test data: vld-ts (Zod / Valibot from vld schemas), vld-fake.
  • Databases: vld-sqlx, vld-diesel, vld-sea, vld-surrealdb.
  • Messaging: vld-redis, vld-lapin.
  • Config and CLI: vld-config, vld-clap.
  • UI: vld-dioxus, vld-leptos, vld-tauri.

Next: install the crate and follow the quick start.