Skip to content

zod-rs vs the garde Crate

garde is a modern rewrite of the validator crate: you derive Validate on a struct, annotate fields with #[garde(...)] rules, and validate instances after deserializing them. zod-rs works one level earlier — it validates raw JSON values against schemas, so parsing and validation happen in one step.

Featurezod-rsgarde
Validation targetRaw JSON valuesAlready-deserialized structs
Schema definitionDerive macro or runtime buildersStruct attributes only
Runtime flexibilityBuild and compose schemas at runtimeCompile-time only
Parse + validateOne step from JSONDeserialize first, then validate
Error messagesFull path context (user.addresses[0].zip)Path-aware field errors
Context-aware rulesCustom Schema implementationsBuilt-in validation context
TypeScript codegenGenerates Zod schemas from Rust typesNot supported
Framework integrationBuilt-in Axum supportVia axum_garde and similar crates
i18nBuilt-in localized error messagesCustom messages, no built-in locales

garde is a good choice when your data is already a well-typed Rust struct and you only need to check invariants on it — especially if you want context-aware rules (validating a field against external state) with minimal ceremony. If that’s your whole problem, garde solves it well.

zod-rs earns its place when validation starts at the JSON boundary: API request bodies, webhooks, config files, or anywhere you’d otherwise deserialize first and validate second. It also brings runtime-composable schemas and TypeScript Zod codegen for sharing rules with a frontend — neither of which fits garde’s model.

use garde::Validate;
#[derive(Validate)]
struct User {
#[garde(length(min = 3, max = 20))]
username: String,
#[garde(email)]
email: String,
#[garde(range(min = 13, max = 120))]
age: u32,
}
use zod_rs::prelude::*;
#[derive(ZodSchema)]
struct User {
#[zod(min_length(3), max_length(20))]
username: String,
#[zod(email)]
email: String,
#[zod(min(13.0), max(120.0), int)]
age: u32,
}
// garde: deserialize, then validate the struct
let user: User = serde_json::from_value(json_data)?;
user.validate()?;
// zod-rs: validate the JSON and get the struct in one call
let user = User::validate_and_parse(&json_data)?;

With garde, malformed JSON fails in serde with a serde error, while invalid values fail later in garde with a garde error — two error shapes from one input. With zod-rs, both come back as one structured validation result.

// Build, store, and compose schemas at runtime — not possible with attribute-only validation
let schema = object()
.field("name", string().min(2))
.field("email", string().email());

See Schema Composition for reuse patterns.