domainstack 1.1.1

Write validation once, use everywhere: Rust rules auto-generate JSON Schema + OpenAPI + TypeScript/Zod. WASM browser validation. Axum/Actix/Rocket adapters.
Documentation

domainstack

Blackwell Systems™ Rust Version Crates.io Documentation Version CI codecov License: MIT OR Apache-2.0 Sponsor

Full-stack validation ecosystem for Rust web services

Define validation once. Get runtime checks, OpenAPI schemas, TypeScript types, and framework integration—from one source of truth.

Rust Domain                        Frontend
     |                                 |
#[derive(Validate, ToSchema)]    domainstack zod
     |                                 |
     v                                 v
.validate()?                      Zod schemas
     |                                 |
     v                                 v
Axum / Actix / Rocket  <------>  Same rules,
     |                            both sides
     v
Structured errors (field-level, indexed paths)

Progressive adoption — use what you need:

Need Start With
Just validation domainstack core
+ derive macros + domainstack-derive
+ OpenAPI schemas + domainstack-schema
+ Axum/Actix/Rocket + framework adapter
+ TypeScript/Zod + domainstack-cli

Why domainstack?

domainstack turns untrusted input into valid-by-construction domain objects with stable, field-level errors your APIs and UIs can depend on.

Built for the boundary you actually live at:

HTTP/JSON → DTOs → Domain (validated) → Business logic

Two validation gates

Gate 1: Serde (decode + shape) — JSON → DTO. Fails on invalid JSON, type mismatches, missing required fields.

Gate 2: Domain (construct + validate) — DTO → Domain. Produces structured field-level errors your APIs depend on.

After domain validation succeeds, you can optionally run async/context validation (DB/API checks like uniqueness, rate limits, authorization) as a post-validation phase.

vs. validator/garde

validator and garde focus on: "Is this struct valid?"

domainstack focuses on the full ecosystem:

  • DTO → Domain conversion with field-level error paths
  • Rules as composable values (.and(), .or(), .when())
  • Async validation with context (DB checks, API calls)
  • Framework adapters that map errors to structured HTTP responses
  • OpenAPI schemas generated from the same validation rules
  • TypeScript/Zod codegen for frontend parity

If you want valid-by-construction domain types with errors that map cleanly to forms and clients, domainstack is purpose-built for that.

What that gives you

  • Domain-first modeling: make invalid states difficult (or impossible) to represent
  • Composable rule algebra: reusable rules with .and(), .or(), .when()
  • Structured error paths: rooms[0].adults, guest.email.value (UI-friendly)
  • Cross-field validation: invariants like password confirmation, date ranges
  • Type-state tracking: phantom types to enforce "validated" at compile time
  • Schema + client parity: generate OpenAPI (and TypeScript/Zod via CLI) from the same Rust rules
  • Framework adapters: one-line boundary extraction (Axum / Actix / Rocket)
  • Lean core: zero-deps base, opt-in features for regex / async / chrono / serde

Table of Contents

Quick Start

Dependencies (add to Cargo.toml):

[dependencies]
domainstack = { version = "1.0", features = ["derive", "regex", "chrono"] }
domainstack-derive = "1.0"
domainstack-axum = "1.0"  # Or domainstack-actix if using Actix-web
serde = { version = "1", features = ["derive"] }
chrono = "0.4"
axum = "0.7"

Complete example (with all imports):

use axum::Json;
use chrono::NaiveDate;
use domainstack::prelude::*;
use domainstack_axum::{DomainJson, ErrorResponse};
use domainstack_derive::{ToSchema, Validate};
use serde::Deserialize;

// DTO from HTTP/JSON (untrusted input)
#[derive(Deserialize)]
struct BookingDto {
    guest_email: String,
    check_in: String,
    check_out: String,
    rooms: Vec<RoomDto>,
}

#[derive(Deserialize)]
struct RoomDto {
    adults: u8,
    children: u8,
}

// Domain models with validation rules (invalid states impossible)
#[derive(Validate, ToSchema)]
#[validate(
    check = "self.check_in < self.check_out",
    message = "Check-out must be after check-in"
)]
struct Booking {
    #[validate(email, max_len = 255)]
    guest_email: String,

    check_in: NaiveDate,
    check_out: NaiveDate,

    #[validate(min_items = 1, max_items = 5)]
    #[validate(each(nested))]
    rooms: Vec<Room>,
}

#[derive(Validate, ToSchema)]
struct Room {
    #[validate(range(min = 1, max = 4))]
    adults: u8,

    #[validate(range(min = 0, max = 3))]
    children: u8,
}

// TryFrom: DTO → Domain conversion with validation
impl TryFrom<BookingDto> for Booking {
    type Error = ValidationError;

    fn try_from(dto: BookingDto) -> Result<Self, Self::Error> {
        let booking = Self {
            guest_email: dto.guest_email,
            check_in: NaiveDate::parse_from_str(&dto.check_in, "%Y-%m-%d")
                .map_err(|_| ValidationError::single("check_in", "invalid_date", "Invalid date format"))?,
            check_out: NaiveDate::parse_from_str(&dto.check_out, "%Y-%m-%d")
                .map_err(|_| ValidationError::single("check_out", "invalid_date", "Invalid date format"))?,
            rooms: dto.rooms.into_iter().map(|r| Room {
                adults: r.adults,
                children: r.children,
            }).collect(),
        };

        booking.validate()?;  // Validates all fields + cross-field rules!
        Ok(booking)
    }
}

// Axum handler: one-line extraction with automatic validation
type BookingJson = DomainJson<Booking, BookingDto>;

async fn create_booking(
    BookingJson { domain: booking, .. }: BookingJson
) -> Result<Json<Booking>, ErrorResponse> {
    // booking is GUARANTEED valid here - use with confidence!
    save_to_db(booking).await
}

On validation failure, automatic structured errors:

{
  "status": 400,
  "message": "Validation failed with 3 errors",
  "details": {
    "fields": {
      "guest_email": [
        {"code": "invalid_email", "message": "Invalid email format"}
      ],
      "rooms[0].adults": [
        {"code": "out_of_range", "message": "Must be between 1 and 4"}
      ],
      "rooms[1].children": [
        {"code": "out_of_range", "message": "Must be between 0 and 3"}
      ]
    }
  }
}

Auto-generated TypeScript/Zod from the same Rust code:

// Zero duplication - generated from Rust validation rules!
export const bookingSchema = z.object({
  guest_email: z.string().email().max(255),
  check_in: z.string(),
  check_out: z.string(),
  rooms: z.array(z.object({
    adults: z.number().min(1).max(4),
    children: z.number().min(0).max(3)
  })).min(1).max(5)
}).refine(
  (data) => data.check_in < data.check_out,
  { message: "Check-out must be after check-in" }
);

Installation

[dependencies]
domainstack = { version = "1.0", features = ["derive", "regex"] }
domainstack-axum = "1.0"  # or domainstack-actix, domainstack-rocket

# Optional
domainstack-schema = "1.0"  # OpenAPI generation

For complete installation guide, feature flags, and companion crates, see INSTALLATION.md

Key Features

  • 37 Validation Rules - String, numeric, collection, and date/time validation → RULES.md
  • Derive Macros - #[derive(Validate)] for declarative validation → DERIVE_MACRO.md
  • Composable Rules - .and(), .or(), .when() combinators for complex logic
  • Nested Validation - Automatic path tracking for deeply nested structures
  • Collection Validation - Array indices in error paths (items[0].field)
  • Serde Integration - Validate during deserialization → SERDE_INTEGRATION.md
  • Async Validation - Database/API checks with AsyncValidateASYNC_VALIDATION.md
  • Cross-Field Validation - Password confirmation, date ranges → CROSS_FIELD_VALIDATION.md
  • Type-State Validation - Compile-time guarantees with phantom types → TYPE_STATE.md
  • OpenAPI Schema Generation - Auto-generate schemas from rules → OPENAPI_SCHEMA.md
  • JSON Schema Generation - Draft 2020-12 schemas from validation rules → CLI_GUIDE.md
  • Framework Adapters - Axum, Actix-web, Rocket extractors → HTTP_INTEGRATION.md

Examples

Derive Macro (Recommended)

Most validation is declarative with #[derive(Validate)]:

#[derive(Debug, Validate)]
struct User {
    #[validate(length(min = 3, max = 20))]
    #[validate(alphanumeric)]
    username: String,

    #[validate(email)]
    #[validate(max_len = 255)]
    email: String,

    #[validate(range(min = 18, max = 120))]
    age: u8,
}

// Validate all fields at once
let user = User { username, email, age };
user.validate()?;  // [ok] Validates all constraints

See the examples folder for more:

  • Nested validation with path tracking
  • Collection item validation
  • Manual validation for newtypes
  • Cross-field validation
  • Async validation
  • Type-state patterns
  • OpenAPI schema generation
  • Framework integration examples

Framework Adapters

One-line DTO→Domain extractors with automatic validation and structured error responses:

Framework Crate Extractor
Axum domainstack-axum DomainJson<T, Dto>
Actix-web domainstack-actix DomainJson<T, Dto>
Rocket domainstack-rocket DomainJson<T, Dto>

HTTP Integration Guide

Running Examples

See examples/README.md for instructions on running all examples.

Crates

8 publishable crates for modular adoption:

Category Crates
Core domainstack, domainstack-derive, domainstack-schema, domainstack-envelope
Web Framework Integrations domainstack-axum, domainstack-actix, domainstack-rocket, domainstack-http

4 example crates (repository only): domainstack-examples, examples-axum, examples-actix, examples-rocket

Complete Crate List - Detailed descriptions and links

Documentation

Getting Started
Core Concepts Valid-by-construction types, error paths
Domain Modeling DTO→Domain, smart constructors
Installation Feature flags, companion crates
Guides
Derive Macro #[derive(Validate)] reference
Validation Rules All 37 built-in rules
Error Handling Violations, paths, i18n
HTTP Integration Axum / Actix / Rocket
Advanced
Async Validation DB/API checks, context
OpenAPI Schema Generate from rules
CLI / Zod / JSON Schema TypeScript and JSON Schema codegen
Serde Integration Validate on deserialize

Reference: API Docs · Architecture · Examples

License

Apache 2.0

Author

Dayna Blackwell - blackwellsystems@protonmail.com

Contributing

This is an early-stage project. Issues and pull requests are welcome!