| Crates.io | domainstack |
| lib.rs | domainstack |
| version | 1.1.1 |
| created_at | 2026-01-06 13:49:17.272566+00 |
| updated_at | 2026-01-07 23:36:32.470415+00 |
| description | Write validation once, use everywhere: Rust rules auto-generate JSON Schema + OpenAPI + TypeScript/Zod. WASM browser validation. Axum/Actix/Rocket adapters. |
| homepage | |
| repository | https://github.com/blackwell-systems/domainstack |
| max_upload_size | |
| id | 2025893 |
| size | 623,367 |
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 |
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
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.
validator and garde focus on: "Is this struct valid?"
domainstack focuses on the full ecosystem:
.and(), .or(), .when())If you want valid-by-construction domain types with errors that map cleanly to forms and clients, domainstack is purpose-built for that.
.and(), .or(), .when()rooms[0].adults, guest.email.value (UI-friendly)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" }
);
[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
#[derive(Validate)] for declarative validation → DERIVE_MACRO.md.and(), .or(), .when() combinators for complex logicitems[0].field)AsyncValidate → ASYNC_VALIDATION.mdMost 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:
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> |
See examples/README.md for instructions on running all examples.
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
| 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
Apache 2.0
Dayna Blackwell - blackwellsystems@protonmail.com
This is an early-stage project. Issues and pull requests are welcome!