{"id":29228062,"url":"https://github.com/maulanasdqn/zod-rs","last_synced_at":"2026-04-28T10:07:21.846Z","repository":{"id":301859076,"uuid":"1010518585","full_name":"maulanasdqn/zod-rs","owner":"maulanasdqn","description":"A Zod Implementation in Rust","archived":false,"fork":false,"pushed_at":"2026-03-14T11:26:32.000Z","size":308,"stargazers_count":44,"open_issues_count":1,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-27T22:39:40.139Z","etag":null,"topics":["hacktoberfest","rust","validation","zod"],"latest_commit_sha":null,"homepage":"https://zod-rs.msdqn.dev","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maulanasdqn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-29T08:41:03.000Z","updated_at":"2026-03-16T00:46:00.000Z","dependencies_parsed_at":"2025-06-29T08:46:01.710Z","dependency_job_id":"7ab493df-5de9-42b8-a318-8881b75af263","html_url":"https://github.com/maulanasdqn/zod-rs","commit_stats":null,"previous_names":["maulanasdqn/zod-rs"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/maulanasdqn/zod-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maulanasdqn%2Fzod-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maulanasdqn%2Fzod-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maulanasdqn%2Fzod-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maulanasdqn%2Fzod-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maulanasdqn","download_url":"https://codeload.github.com/maulanasdqn/zod-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maulanasdqn%2Fzod-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32375783,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T09:24:15.638Z","status":"ssl_error","status_checked_at":"2026-04-28T09:24:15.071Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","rust","validation","zod"],"created_at":"2025-07-03T10:01:51.842Z","updated_at":"2026-04-28T10:07:21.840Z","avatar_url":"https://github.com/maulanasdqn.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zod-rs\n\n![image](https://github.com/user-attachments/assets/033617ef-3b7d-4c03-87e5-b082f98a26d5)\n\n**A Rust implementation inspired by Zod for schema validation**\n\n[![Crates.io](https://img.shields.io/crates/v/zod-rs.svg)](https://crates.io/crates/zod-rs)\n[![Documentation](https://docs.rs/zod-rs/badge.svg)](https://docs.rs/zod-rs)\n[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE)\n\nzod-rs is a TypeScript-first schema validation library with static type inference, inspired by [Zod](https://github.com/colinhacks/zod). It provides a simple and intuitive API for validating JSON data with comprehensive error reporting.\n\n## Features\n\n- **Type-safe validation** - Full type safety with compile-time guarantees\n- **Zero dependencies** - Lightweight core with optional integrations\n- **Rich error messages** - Detailed validation errors with path information\n- **Composable schemas** - Build complex validation rules from simple primitives\n- **Framework integration** - Built-in support for Axum and other web frameworks\n- **High performance** - Efficient validation with minimal overhead\n- **Developer friendly** - Intuitive API similar to TypeScript Zod\n- **Schema inference** - Automatically generate schemas from Rust structs and enums\n- **Attribute macros** - Rich validation constraints via `#[zod(...)]` attributes\n- **Validator replacement** - Drop-in replacement for the `validator` crate\n- **Internationalization (i18n)** — Localized error messages and validation feedback\n- **Enum support** - Full enum validation with unit, tuple, and struct variants\n- **TypeScript codegen** - Generate TypeScript Zod schemas from Rust types\n\n## Installation\n\nAdd zod-rs to your `Cargo.toml`:\n\n```toml\n[dependencies]\nzod-rs = \"1.0\"\n\n# Optional: for web framework integration\nzod-rs = { version = \"1.0\", features = [\"axum\"] }\n\n# For TypeScript Zod schema generation\nzod-rs = { version = \"1.0\", features = [\"ts\"] }\n# Or use the standalone crate\nzod-rs-ts = \"1.0\"\n\n# For schema derivation from structs (recommended)\nzod-rs = \"1.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\n```\n\n## Quick Start\n\n```rust\nuse serde_json::json;\nuse zod_rs::prelude::*;\n\nfn main() {\n    // Define a schema\n    let user_schema = object()\n        .field(\"name\", string().min(2).max(50))\n        .field(\"email\", string().email())\n        .field(\"age\", number().min(0.0).max(120.0).int());\n\n    // Validate data\n    let user_data = json!({\n        \"name\": \"Alice\",\n        \"email\": \"alice@example.com\",\n        \"age\": 25\n    });\n\n    match user_schema.safe_parse(\u0026user_data) {\n        Ok(validated_data) =\u003e println!(\"Valid: {:?}\", validated_data),\n        Err(errors) =\u003e println!(\"Invalid: {}\", errors),\n    }\n}\n```\n\n## API Reference\n\n### Basic Types\n\n#### String Validation\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\n// Basic string\nlet schema = string();\nassert!(schema.safe_parse(\u0026json!(\"hello\")).is_ok());\n\n// String with length constraints\nlet schema = string().min(3).max(10);\nassert!(schema.safe_parse(\u0026json!(\"hello\")).is_ok());\nassert!(schema.safe_parse(\u0026json!(\"hi\")).is_err());\n\n// Exact length\nlet schema = string().length(5);\nassert!(schema.safe_parse(\u0026json!(\"hello\")).is_ok());\n\n// Pattern matching\nlet schema = string().regex(r\"^[a-zA-Z]+$\");\nassert!(schema.safe_parse(\u0026json!(\"hello\")).is_ok());\nassert!(schema.safe_parse(\u0026json!(\"hello123\")).is_err());\n\n// Email validation\nlet schema = string().email();\nassert!(schema.safe_parse(\u0026json!(\"user@example.com\")).is_ok());\n\n// URL validation\nlet schema = string().url();\nassert!(schema.safe_parse(\u0026json!(\"https://example.com\")).is_ok());\n```\n\n#### Number Validation\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\n// Basic number\nlet schema = number();\nassert!(schema.safe_parse(\u0026json!(42.5)).is_ok());\n\n// Integer only\nlet schema = number().int();\nassert!(schema.safe_parse(\u0026json!(42)).is_ok());\nassert!(schema.safe_parse(\u0026json!(42.5)).is_err());\n\n// Range constraints\nlet schema = number().min(0.0).max(100.0);\nassert!(schema.safe_parse(\u0026json!(50)).is_ok());\nassert!(schema.safe_parse(\u0026json!(-1)).is_err());\n\n// Positive numbers\nlet schema = number().positive();\nassert!(schema.safe_parse(\u0026json!(1)).is_ok());\nassert!(schema.safe_parse(\u0026json!(0)).is_err());\n\n// Non-negative numbers\nlet schema = number().nonnegative();\nassert!(schema.safe_parse(\u0026json!(0)).is_ok());\nassert!(schema.safe_parse(\u0026json!(-1)).is_err());\n\n// Finite numbers (excludes NaN, Infinity)\nlet schema = number().finite();\nassert!(schema.safe_parse(\u0026json!(42.0)).is_ok());\n```\n\n#### Boolean Validation\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\nlet schema = boolean();\nassert!(schema.safe_parse(\u0026json!(true)).is_ok());\nassert!(schema.safe_parse(\u0026json!(false)).is_ok());\nassert!(schema.safe_parse(\u0026json!(\"true\")).is_err());\n```\n\n#### Literal Validation\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\n// String literal\nlet schema = literal(\"active\".to_string());\nassert!(schema.safe_parse(\u0026json!(\"active\")).is_ok());\nassert!(schema.safe_parse(\u0026json!(\"inactive\")).is_err());\n\n// Number literal\nlet schema = literal(42.0);\nassert!(schema.safe_parse(\u0026json!(42.0)).is_ok());\nassert!(schema.safe_parse(\u0026json!(43.0)).is_err());\n```\n\n### Complex Types\n\n#### Array Validation\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\n// Array of strings\nlet schema = array(string());\nassert!(schema.safe_parse(\u0026json!([\"a\", \"b\", \"c\"])).is_ok());\n\n// Array with length constraints\nlet schema = array(string()).min(1).max(5);\nassert!(schema.safe_parse(\u0026json!([\"a\"])).is_ok());\nassert!(schema.safe_parse(\u0026json!([])).is_err());\n\n// Array with exact length\nlet schema = array(number()).length(3);\nassert!(schema.safe_parse(\u0026json!([1, 2, 3])).is_ok());\n\n// Nested arrays\nlet schema = array(array(string()));\nassert!(schema.safe_parse(\u0026json!([[\"a\", \"b\"], [\"c\", \"d\"]])).is_ok());\n```\n\n#### Object Validation\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\n// Simple object\nlet schema = object()\n    .field(\"name\", string())\n    .field(\"age\", number());\n\nlet data = json!({\"name\": \"Alice\", \"age\": 25});\nassert!(schema.safe_parse(\u0026data).is_ok());\n\n// Object with optional fields\nlet schema = object()\n    .field(\"name\", string())\n    .optional_field(\"bio\", string());\n\nlet data = json!({\"name\": \"Alice\"});\nassert!(schema.safe_parse(\u0026data).is_ok());\n\n// Strict mode (no additional properties)\nlet schema = object()\n    .field(\"name\", string())\n    .strict();\n```\n\n#### Optional Values\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\nlet schema = optional(string());\nassert!(schema.safe_parse(\u0026json!(null)).is_ok());\nassert!(schema.safe_parse(\u0026json!(\"hello\")).is_ok());\n\n// Method chaining\nlet schema = string().optional();\n```\n\n#### Union Types\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\nlet schema = union()\n    .variant(string())\n    .variant(number());\n\nassert!(schema.safe_parse(\u0026json!(\"hello\")).is_ok());\nassert!(schema.safe_parse(\u0026json!(42)).is_ok());\nassert!(schema.safe_parse(\u0026json!(true)).is_err());\n\n// Literal unions (enums)\nlet schema = union()\n    .variant(literal(\"small\".to_string()))\n    .variant(literal(\"medium\".to_string()))\n    .variant(literal(\"large\".to_string()));\n```\n\n### Schema Methods\n\nAll schemas support these methods:\n\n#### `parse(value)` - Parse with panic on error\n\n```rust\nlet schema = string();\nlet result = schema.parse(\u0026json!(\"hello\")); // Panics on validation failure\n```\n\n#### `safe_parse(value)` - Parse with Result\n\n```rust\nlet schema = string();\nmatch schema.safe_parse(\u0026json!(\"hello\")) {\n    Ok(value) =\u003e println!(\"Valid: {}\", value),\n    Err(errors) =\u003e println!(\"Invalid: {}\", errors),\n}\n```\n\n#### `validate(value)` - Alias for safe_parse\n\n```rust\nlet schema = string();\nlet result = schema.validate(\u0026json!(\"hello\"));\n```\n\n## Complex Examples\n\n### Struct Validation\n\n```rust\nuse serde::{Deserialize, Serialize};\nuse serde_json::json;\nuse zod_rs::prelude::*;\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct User {\n    username: String,\n    email: String,\n    age: f64,\n    interests: Vec\u003cString\u003e,\n}\n\nfn user_schema() -\u003e impl Schema\u003cValue\u003e {\n    object()\n        .field(\"username\", string().min(3).max(20).regex(r\"^[a-zA-Z0-9_]+$\"))\n        .field(\"email\", string().email())\n        .field(\"age\", number().min(13.0).max(120.0).int())\n        .field(\"interests\", array(string()).min(1).max(10))\n}\n\nfn main() {\n    let user_data = json!({\n        \"username\": \"alice_dev\",\n        \"email\": \"alice@example.com\",\n        \"age\": 28,\n        \"interests\": [\"rust\", \"programming\"]\n    });\n\n    // Validate and deserialize\n    match user_schema().validate(\u0026user_data) {\n        Ok(_) =\u003e {\n            let user: User = serde_json::from_value(user_data).unwrap();\n            println!(\"Valid user: {:?}\", user);\n        }\n        Err(errors) =\u003e {\n            println!(\"Validation failed: {}\", errors);\n        }\n    }\n}\n```\n\n### Nested Objects\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\nfn address_schema() -\u003e impl Schema\u003cValue\u003e {\n    object()\n        .field(\"street\", string().min(1))\n        .field(\"city\", string().min(1))\n        .field(\"country\", string().length(2)) // ISO country code\n        .field(\"zip\", string().regex(r\"^\\d{5}(-\\d{4})?$\"))\n}\n\nfn user_schema() -\u003e impl Schema\u003cValue\u003e {\n    object()\n        .field(\"name\", string())\n        .field(\"email\", string().email())\n        .field(\"address\", address_schema())\n        .optional_field(\"billing_address\", address_schema())\n}\n\nlet user_data = json!({\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\",\n    \"address\": {\n        \"street\": \"123 Main St\",\n        \"city\": \"Boston\",\n        \"country\": \"US\",\n        \"zip\": \"02101\"\n    }\n});\n\nassert!(user_schema().safe_parse(\u0026user_data).is_ok());\n```\n\n## Web Framework Integration\n\n### Axum Integration\n\nEnable the `axum` feature in your `Cargo.toml`:\n\n```toml\n[dependencies]\nzod-rs = { version = \"1.0\", features = [\"axum\"] }\naxum = \"0.7\"\ntokio = { version = \"1.0\", features = [\"full\"] }\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\n```\n\n```rust\nuse axum::{\n    extract::Json,\n    http::StatusCode,\n    response::{IntoResponse, Json as ResponseJson},\n    routing::post,\n    Router,\n};\nuse serde::{Deserialize, Serialize};\nuse serde_json::Value;\nuse zod_rs::prelude::*;\n\n#[derive(Debug, Serialize, Deserialize)]\nstruct CreateUserRequest {\n    name: String,\n    email: String,\n    age: f64,\n}\n\n#[derive(Serialize)]\nstruct ApiResponse\u003cT\u003e {\n    success: bool,\n    data: Option\u003cT\u003e,\n    errors: Option\u003cVec\u003cString\u003e\u003e,\n}\n\nfn user_schema() -\u003e impl Schema\u003cValue\u003e {\n    object()\n        .field(\"name\", string().min(2).max(50))\n        .field(\"email\", string().email())\n        .field(\"age\", number().min(13.0).max(120.0).int())\n}\n\nasync fn create_user(Json(payload): Json\u003cValue\u003e) -\u003e impl IntoResponse {\n    match user_schema().validate(\u0026payload) {\n        Ok(_) =\u003e {\n            let user: CreateUserRequest = serde_json::from_value(payload).unwrap();\n            (\n                StatusCode::CREATED,\n                ResponseJson(ApiResponse {\n                    success: true,\n                    data: Some(user),\n                    errors: None,\n                }),\n            )\n        }\n        Err(validation_result) =\u003e {\n            let errors: Vec\u003cString\u003e = validation_result\n                .issues\n                .iter()\n                .map(|issue| issue.to_string())\n                .collect();\n\n            (\n                StatusCode::BAD_REQUEST,\n                ResponseJson(ApiResponse::\u003cCreateUserRequest\u003e {\n                    success: false,\n                    data: None,\n                    errors: Some(errors),\n                }),\n            )\n        }\n    }\n}\n\n#[tokio::main]\nasync fn main() {\n    let app = Router::new().route(\"/users\", post(create_user));\n\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    axum::serve(listener, app).await.unwrap();\n}\n```\n\n## Error Handling\n\nzod-rs provides detailed error information with path tracking:\n\n```rust\nuse zod_rs::prelude::*;\nuse serde_json::json;\n\nlet schema = object()\n    .field(\"user\", object()\n        .field(\"name\", string().min(2))\n        .field(\"email\", string().email())\n    );\n\nlet invalid_data = json!({\n    \"user\": {\n        \"name\": \"A\",\n        \"email\": \"invalid-email\"\n    }\n});\n\nmatch schema.safe_parse(\u0026invalid_data) {\n    Err(errors) =\u003e {\n        println!(\"{}\", errors);\n        // Output:\n        //   - user.name: Too big: expected string to have \u003e= 2 characters\n        //   - user.email: Invalid email address\n    }\n    _ =\u003e {}\n}\n```\n\n### Error Types\n\n- `ValidationError::Required` - Missing required field\n- `ValidationError::InvalidType` - Wrong data type\n- `ValidationError::InvalidValue` - Provided value does not match the expected value\n- `ValidationError::InvalidValues` - Provided value does not match any of the expected values.\n- `ValidationError::TooSmall` / `TooBig` - Value or lenght out of range (String, Number, Array ... etc)\n- `ValidationError::InvalidFormat` - String format validation (starts with , ends with, includes, regex, ... etc)\n- `ValidationError::InvalidNumber` - Invalid number constraint (finite, positive, ... etc)\n- `ValidationError::UnrecognizedKeys` - Object with unrecognized keys\n- `ValidationError::InvalidUnion` - No union matching\n- `ValidationError::Custom` - Custom validation errors\n\n## Internationalization (i18n)\n\nzod-rs comes with built-in locale support so you can get validation errors in different languages.\n\n### Currently supported\n\n- English (default)\n- Arabic\n\nExample\n\n```rust\nuse serde_json::json;\nuse zod_rs::prelude::*;\n\nlet login_schema = object()\n    .field(\"email\", string().ends_with(\"@domain.com\"))\n    .field(\"password\", string().min(8))\n    .strict();\n\nlet input = json!({\n    \"email\": \"john@example.com\",\n    \"password\": \"Strongpassword123\"\n});\n\nmatch login_schema.safe_parse(\u0026input) {\n    Ok(output) =\u003e println!(\"Valid login: {output}\"),\n    Err(err) =\u003e println!(\"{}\", err.local(Locale::Ar)),\n}\n```\n\nWant to add a new language? Missing a translation? Open an issue or PR on GitHub — contributions are welcome.\n\n## Advanced Usage\n\n### Schema Inference from Structs\n\nzod-rs provides a powerful derive macro that automatically generates validation schemas from Rust structs, making it an excellent replacement for the `validator` crate.\n\n```rust\nuse serde::{Deserialize, Serialize};\nuse serde_json::json;\nuse zod_rs::prelude::*;\n\n#[derive(Debug, Serialize, Deserialize, ZodSchema)]\nstruct User {\n    #[zod(min_length(3), max_length(20), regex(r\"^[a-zA-Z0-9_]+$\"))]\n    username: String,\n\n    #[zod(email)]\n    email: String,\n\n    #[zod(min(13.0), max(120.0), int)]\n    age: u32,\n\n    #[zod(min_length(1), max_length(10))]\n    interests: Vec\u003cString\u003e,\n\n    bio: Option\u003cString\u003e,\n\n    #[zod(nonnegative)]\n    score: f64,\n\n    is_active: bool,\n}\n\nlet user_data = json!({\n    \"username\": \"alice_dev\",\n    \"email\": \"alice@example.com\",\n    \"age\": 28,\n    \"interests\": [\"rust\", \"programming\"],\n    \"score\": 95.5,\n    \"is_active\": true\n});\n\nmatch User::validate_and_parse(\u0026user_data) {\n    Ok(user) =\u003e println!(\"Valid user: {:?}\", user),\n    Err(e) =\u003e println!(\"Invalid: {}\", e),\n}\n\nlet schema = User::schema();\nmatch schema.validate(\u0026user_data) {\n    Ok(_) =\u003e println!(\"Schema validation passed\"),\n    Err(e) =\u003e println!(\"Schema validation failed: {}\", e),\n}\n\nlet user_from_json = User::from_json(r#\"{\"username\":\"test\",\"email\":\"test@example.com\",...}\"#)?;\n```\n\n#### Available Validation Attributes\n\nThe `#[zod(...)]` attribute supports the following constraints:\n\n**String Validation:**\n\n- `min_length(n)` - Minimum string length\n- `max_length(n)` - Maximum string length\n- `starts_with(\"value\")` - String starts with a given value\n- `ends_with(\"value\")` - String ends with a given value\n- `includes(\"value\")` - String includes a given value\n- `length(n)` - Exact string length\n- `email` - Email format validation\n- `url` - URL format validation\n- `regex(\"pattern\")` - Regular expression pattern matching\n\n**Number Validation:**\n\n- `min(n)` - Minimum value\n- `max(n)` - Maximum value\n- `int` - Integer only (no decimals)\n- `positive` - Must be positive (\u003e 0)\n- `negative` - Must be negative (\u003c 0)\n- `nonnegative` - Must be non-negative (\u003e= 0)\n- `nonpositive` - Must be non-positive (\u003c= 0)\n- `finite` - Must be finite (excludes NaN, Infinity)\n\n**Array Validation:**\n\n- `min_length(n)` - Minimum array length\n- `max_length(n)` - Maximum array length\n- `length(n)` - Exact array length\n\n#### Nested Structs\n\nThe derive macro automatically handles nested structs:\n\n```rust\n#[derive(Debug, Serialize, Deserialize, ZodSchema)]\nstruct Address {\n    #[zod(min_length(5), max_length(200))]\n    street: String,\n\n    #[zod(min_length(2), max_length(50))]\n    city: String,\n\n    #[zod(length(2))]\n    country_code: String,\n}\n\n#[derive(Debug, Serialize, Deserialize, ZodSchema)]\nstruct UserProfile {\n    #[zod(min_length(2), max_length(50))]\n    name: String,\n\n    #[zod(email)]\n    email: String,\n\n    address: Option\u003cAddress\u003e,\n}\n```\n\n#### Generated Methods\n\nThe `ZodSchema` derive macro generates the following methods:\n\n- `schema()` - Returns the validation schema\n- `validate_and_parse(value)` - Validates and deserializes JSON value\n- `from_json(json_str)` - Validates and parses from JSON string\n- `validate_json(json_str)` - Validates JSON string (returns Value)\n\n### Enum Support\n\nzod-rs fully supports Rust enums with the `ZodSchema` derive macro. Enums are validated using the externally-tagged format (serde default).\n\n```rust\nuse serde::{Deserialize, Serialize};\nuse serde_json::json;\nuse zod_rs::prelude::*;\n\n// Unit variants\n#[derive(Debug, Serialize, Deserialize, ZodSchema)]\nenum Status {\n    Active,\n    Inactive,\n    Pending,\n}\n\n// Tuple variants\n#[derive(Debug, Serialize, Deserialize, ZodSchema)]\nenum Message {\n    Text(String),\n    Number(i32),\n    Coords(i32, i32),\n}\n\n// Struct variants\n#[derive(Debug, Serialize, Deserialize, ZodSchema)]\nenum Event {\n    Click { x: i32, y: i32 },\n    Scroll { delta: f64 },\n}\n\n// Mixed variants\n#[derive(Debug, Serialize, Deserialize, ZodSchema)]\nenum ApiResponse {\n    Success,\n    Data(String),\n    Error { code: i32, message: String },\n}\n\nfn main() {\n    // Unit variant: {\"Active\": null}\n    let status = json!({\"Active\": null});\n    assert!(Status::validate_and_parse(\u0026status).is_ok());\n\n    // Tuple variant (single): {\"Text\": \"hello\"}\n    let msg = json!({\"Text\": \"hello\"});\n    assert!(Message::validate_and_parse(\u0026msg).is_ok());\n\n    // Tuple variant (multiple): {\"Coords\": [10, 20]}\n    let coords = json!({\"Coords\": [10, 20]});\n    assert!(Message::validate_and_parse(\u0026coords).is_ok());\n\n    // Struct variant: {\"Click\": {\"x\": 100, \"y\": 200}}\n    let event = json!({\"Click\": {\"x\": 100, \"y\": 200}});\n    assert!(Event::validate_and_parse(\u0026event).is_ok());\n}\n```\n\n#### JSON Format for Enum Variants\n\n| Variant Type | Rust | JSON |\n|-------------|------|------|\n| Unit | `Status::Active` | `{\"Active\": null}` |\n| Tuple (single) | `Message::Text(\"hi\")` | `{\"Text\": \"hi\"}` |\n| Tuple (multiple) | `Message::Coords(1, 2)` | `{\"Coords\": [1, 2]}` |\n| Struct | `Event::Click { x: 1, y: 2 }` | `{\"Click\": {\"x\": 1, \"y\": 2}}` |\n\n### TypeScript Zod Schema Generation\n\nGenerate TypeScript Zod schemas from your Rust types using the `ZodTs` derive macro.\n\n```rust\nuse zod_rs_ts::ZodTs;\n\n#[derive(ZodTs)]\nstruct User {\n    #[zod(min_length(2), max_length(50))]\n    username: String,\n\n    #[zod(email)]\n    email: String,\n\n    #[zod(min(18.0), max(120.0), int)]\n    age: u32,\n\n    bio: Option\u003cString\u003e,\n}\n\nfn main() {\n    // Generate TypeScript code\n    let ts_code = User::zod_ts();\n    println!(\"{}\", ts_code);\n\n    // Or write to file\n    std::fs::write(\"schemas/user.ts\", ts_code).unwrap();\n}\n```\n\n**Generated TypeScript:**\n\n```typescript\nimport * as z from \"zod\";\n\nexport const UserSchema = z.object({\n  username: z.string().min(2).max(50),\n  email: z.string().email(),\n  age: z.number().int().min(18).max(120),\n  bio: z.string().optional()\n});\n\nexport type User = z.infer\u003ctypeof UserSchema\u003e;\n```\n\n#### Zod Version\n\nThe generated schemas target **Zod v4** by default (`import * as z from \"zod\"`). To emit Zod v3 style imports instead, enable the `zod-v3` feature:\n\n```toml\n[dependencies]\nzod-rs = { version = \"...\", features = [\"ts\", \"zod-v3\"] }\n# or, if depending on zod-rs-ts directly:\nzod-rs-ts = { version = \"...\", features = [\"zod-v3\"] }\n```\n\nWhen using the CLI, pass `--zod-version v3` to switch (defaults to `v4`):\n\n```bash\nzod-rs-ts generate --input src/ --output schemas/ --zod-version v3\n```\n\n#### Standard Schema Compatibility\n\nSchemas produced by `ZodTs` are [Standard Schema](https://github.com/standard-schema/standard-schema) compliant out of the box — this comes from Zod itself (Zod v3.24+ and all Zod v4 schemas implement the `~standard` interface natively). That means the generated output works directly with Standard Schema consumers like TanStack Form, React Hook Form, and other validation-library-agnostic tooling, with no extra wiring required.\n\n#### Enum TypeScript Generation\n\n```rust\n#[derive(ZodTs)]\nenum Status {\n    Active,\n    Inactive,\n}\n\n#[derive(ZodTs)]\nenum Event {\n    Click { x: i32, y: i32 },\n    Scroll { delta: f64 },\n}\n\nfn main() {\n    println!(\"{}\", Status::zod_ts());\n    println!(\"{}\", Event::zod_ts());\n}\n```\n\n**Generated TypeScript:**\n\n```typescript\nexport const StatusSchema = z.union([\n  z.object({ Active: z.null() }),\n  z.object({ Inactive: z.null() })\n]);\n\nexport const EventSchema = z.union([\n  z.object({ Click: z.object({ x: z.number().int(), y: z.number().int() }) }),\n  z.object({ Scroll: z.object({ delta: z.number() }) })\n]);\n```\n\n#### CLI Tool\n\nInstall and use the CLI for batch generation:\n\n```bash\n# Install with CLI feature\ncargo install zod-rs-ts --features cli\n\n# Generate schemas from Rust source files (emits Zod v4 imports by default)\nzod-rs-ts generate --input src/ --output schemas/\n\n# Generate all schemas in a single file\nzod-rs-ts generate --input src/ --output schemas/index.ts --single-file\n\n# Target Zod v3 imports instead\nzod-rs-ts generate --input src/ --output schemas/ --zod-version v3\n```\n\n### Custom Validation\n\n```rust\nuse zod_rs::prelude::*;\nuse zod_rs_util::{ValidationError, ValidateResult};\nuse serde_json::Value;\n\nstruct CustomSchema {\n    min_words: usize,\n}\n\nimpl Schema\u003cString\u003e for CustomSchema {\n    fn validate(\u0026self, value: \u0026Value) -\u003e ValidateResult\u003cString\u003e {\n        let string_val = value.as_str()\n            .ok_or_else(|| ValidationError::invalid_type(\"string\", \"other\"))?\n            .to_string();\n\n        let word_count = string_val.split_whitespace().count();\n        if word_count \u003c self.min_words {\n            return Err(ValidationError::custom(\n                format!(\"Must contain at least {} words\", self.min_words)\n            ).into());\n        }\n\n        Ok(string_val)\n    }\n}\n\nlet schema = CustomSchema { min_words: 3 };\nassert!(schema.safe_parse(\u0026json!(\"hello world rust\")).is_ok());\nassert!(schema.safe_parse(\u0026json!(\"hello world\")).is_err());\n```\n\n### Schema Composition\n\n```rust\nuse zod_rs::prelude::*;\n\nfn email_schema() -\u003e impl Schema\u003cString\u003e {\n    string().email()\n}\n\nfn password_schema() -\u003e impl Schema\u003cString\u003e {\n    string().min(8).regex(r\"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)\")\n}\n\nfn login_schema() -\u003e impl Schema\u003cValue\u003e {\n    object()\n        .field(\"email\", email_schema())\n        .field(\"password\", password_schema())\n}\n```\n\n## Testing\n\nRun the test suite:\n\n```bash\ncargo test\n```\n\nRun examples:\n\n```bash\n# Basic usage\ncargo run --example basic_usage\n\n# Struct validation\ncargo run --example struct_validation\n\n# Derive macro for schema inference\ncargo run --example derive_schema\n\n# Validator crate replacement\ncargo run --example validator_replacement\n\n# TypeScript Zod schema generation\ncargo run --example zod_ts --features ts\n\n# Axum integration\ncargo run --example axum_usage --features axum\n```\n\n## Workspace Structure\n\nThis project uses a Cargo workspace with the following crates:\n\n- **`zod-rs`** - Main validation library with schema types\n- **`zod-rs-macros`** - Derive macros for `ZodSchema`\n- **`zod-rs-ts`** - TypeScript Zod schema generation\n- **`zod-rs-util`** - Utility functions, error handling and i18n\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n### Development Setup\n\n1. Clone the repository\n2. Run tests: `cargo test`\n3. Run examples: `cargo run --example basic_usage`\n4. Format code: `cargo fmt`\n5. Check with clippy: `cargo clippy`\n\n## License\n\nThis project is licensed under either of\n\n- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or \u003chttp://www.apache.org/licenses/LICENSE-2.0\u003e)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or \u003chttp://opensource.org/licenses/MIT\u003e)\n\nat your option.\n\n## Acknowledgments\n\n- Inspired by [Zod](https://github.com/colinhacks/zod) by Colin McDonnell\n- Built for the Rust community\n\n## Related Projects\n\n- [Zod](https://github.com/colinhacks/zod) - TypeScript-first schema validation\n- [Serde](https://github.com/serde-rs/serde) - Rust serialization framework\n- [Validator](https://github.com/Keats/validator) - Rust struct validation\n\n## zod-rs vs Validator Crate\n\nzod-rs provides significant advantages over the traditional `validator` crate:\n\n| Feature                   | zod-rs                            | validator crate             |\n| ------------------------- | --------------------------------- | --------------------------- |\n| **Schema Definition**     | Derive macro with attributes      | Struct attributes only      |\n| **Runtime Flexibility**   | Dynamic schema creation           | Compile-time only           |\n| **Error Messages**        | Detailed with full path context   | Basic field-level errors    |\n| **JSON Integration**      | Built-in JSON validation/parsing  | Manual serde integration    |\n| **Nested Validation**     | Automatic nested struct support   | Manual implementation       |\n| **Schema Reuse**          | Composable and reusable schemas   | Struct-bound validation     |\n| **Type Safety**           | Full type inference               | Limited type information    |\n| **Performance**           | Optimized validation pipeline     | Direct field validation     |\n| **Extensibility**         | Custom validators and schemas     | Custom validation functions |\n| **Framework Integration** | Built-in web framework support    | Manual integration required |\n| **Internationalization**  | Built-in Localized error messages | No i18n support    |\n\n### Migration from Validator Crate\n\n```rust\n// Before: using validator crate\nuse validator::{Validate, ValidationError};\n\n#[derive(Validate)]\nstruct User {\n    #[validate(length(min = 3, max = 20))]\n    username: String,\n\n    #[validate(email)]\n    email: String,\n\n    #[validate(range(min = 13, max = 120))]\n    age: u32,\n}\n\n// After: using zod-rs\nuse zod_rs::prelude::*;\n\n#[derive(ZodSchema)]\nstruct User {\n    #[zod(min_length(3), max_length(20))]\n    username: String,\n\n    #[zod(email)]\n    email: String,\n\n    #[zod(min(13.0), max(120.0), int)]\n    age: u32,\n}\n\n// Enhanced capabilities with zod-rs\nlet user_data = json!({\n    \"username\": \"alice\",\n    \"email\": \"alice@example.com\",\n    \"age\": 25\n});\n\n// Validate and parse in one step\nlet user = User::validate_and_parse(\u0026user_data)?;\n\n// Or validate JSON string directly\nlet user = User::from_json(r#\"{\"username\":\"alice\",...}\"#)?;\n\n// Reuse schema for different purposes\nlet schema = User::schema();\nlet is_valid = schema.validate(\u0026user_data).is_ok();\n```\n\n---\n\nMade by [Maulana Sodiqin](https://github.com/maulanasdqn)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaulanasdqn%2Fzod-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaulanasdqn%2Fzod-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaulanasdqn%2Fzod-rs/lists"}