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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# Architecture

Technical architecture of the domainstack validation framework.

## System Overview

```mermaid
%%{init: {'theme':'dark'}}%%
graph TB
    subgraph User["User Code"]
        Domain["Domain Types<br/>(User, Email, Team)"]
        DTO["DTOs<br/>(UserDto, etc)"]
        Handler["HTTP Handlers<br/>(Axum/Actix/Rocket)"]
    end

    subgraph Adapters["Framework Adapters"]
        AxumExt["domainstack-axum<br/>DomainJson extractor"]
        ActixExt["domainstack-actix<br/>DomainJson extractor"]
        RocketExt["domainstack-rocket<br/>DomainJson extractor"]
        HttpShared["domainstack-http<br/>(shared logic)"]
    end

    subgraph Core["domainstack (Core)"]
        Validate["Validate Trait"]
        Rules["Built-in Rules<br/>(email, length, range)"]
        Error["ValidationError<br/>(structured paths)"]
    end

    subgraph Derive["domainstack-derive"]
        Macro["#[derive(Validate, ToSchema)]<br/>Proc Macro"]
    end

    subgraph Envelope["domainstack-envelope"]
        Convert["IntoEnvelopeError<br/>(HTTP errors)"]
    end

    subgraph Schema["domainstack-schema"]
        OpenAPI["OpenAPI Schema<br/>Generation"]
    end

    subgraph WASM["domainstack-wasm"]
        Browser["Browser Validation<br/>(WebAssembly)"]
    end

    subgraph CLI["domainstack-cli"]
        Codegen["TypeScript/Zod<br/>Code Generation"]
    end

    DTO -->|"TryFrom"| Domain
    Domain -->|"implements"| Validate
    Domain -->|"uses"| Rules
    Macro -->|"generates"| Validate
    Macro -->|"generates"| OpenAPI
    Validate -->|"returns"| Error
    Handler -->|"extracts via"| AxumExt
    Handler -->|"extracts via"| ActixExt
    Handler -->|"extracts via"| RocketExt
    AxumExt -->|"uses"| HttpShared
    ActixExt -->|"uses"| HttpShared
    RocketExt -->|"uses"| HttpShared
    HttpShared -->|"converts"| Domain
    HttpShared -->|"maps errors"| Convert
    Error -->|"one line"| Convert
    Convert -->|"produces"| JSON["📄 Structured JSON<br/>{fields: {...}}"]
    Domain -->|"compiles to"| Browser
    Codegen -->|"parses"| Domain

    style Core fill:#1a1a2e,stroke:#16213e,stroke-width:2px,color:#eee
    style Derive fill:#0f3460,stroke:#16213e,stroke-width:2px,color:#eee
    style Envelope fill:#16213e,stroke:#16213e,stroke-width:2px,color:#eee
    style Schema fill:#1a4a5e,stroke:#16213e,stroke-width:2px,color:#eee
    style WASM fill:#4a3a1e,stroke:#16213e,stroke-width:2px,color:#eee
    style CLI fill:#3a2a4e,stroke:#16213e,stroke-width:2px,color:#eee
    style Adapters fill:#2d4a2e,stroke:#16213e,stroke-width:2px,color:#eee
    style User fill:#533483,stroke:#16213e,stroke-width:2px,color:#eee
    style JSON fill:#e94560,stroke:#16213e,stroke-width:2px,color:#eee
```

## Component Architecture

### Core Library (domainstack)

**Purpose**: Foundational validation primitives

**Key Types**:
- `Validate` trait - Core validation interface
- `Rule<T>` - Type-safe validation rules
- `ValidationError` - Structured error collection
- `Path` - Field path construction
- `Violation` - Single validation failure

**Zero Dependencies**: Only uses `std` (regex optional for email)

**Design Principles**:
- Composable rules via `and()`, `or()`, `when()`
- Type safety - Rule<T> ensures type correctness
- Structured paths - Never use string formatting
- Framework agnostic - No web framework dependencies

### Derive Macro (domainstack-derive)

**Purpose**: Code generation for common validation patterns

**Technology**: Procedural macro using `syn`, `quote`, `proc-macro2`

**Attributes Supported**:
1. `#[validate(length(min, max))]` - String length
2. `#[validate(range(min, max))]` - Numeric range
3. `#[validate(nested)]` - Nested struct validation
4. `#[validate(each(...))]` - Collection validation
5. `#[validate(custom = "fn")]` - Custom validation functions

**Code Generation Strategy**:
- Generates `impl Validate for Type` at compile time
- Uses structured Path API (not string concatenation)
- Applies `.prefixed()` for nested/collection paths
- Integrates with core Rule<T> system

### HTTP Integration (domainstack-envelope)

**Purpose**: Bridge validation errors to HTTP responses

**Integration**: error-envelope crate (framework-agnostic)

**API**: `IntoEnvelopeError` trait with one method

**Error Format**:
```json
{
  "code": "VALIDATION",
  "status": 400,
  "retryable": false,
  "details": {
    "fields": {
      "path.to.field": [
        {"code": "error_code", "message": "...", "meta": {...}}
      ]
    }
  }
}
```

**Design Decision**: Trait instead of `From` to avoid orphan rule issues

### Schema Generation (domainstack-schema)

**Purpose**: Generate OpenAPI schemas from validation rules

**Key Types**:
- `Schema` - OpenAPI schema builder
- `ToSchema` trait - Derive macro for schema generation
- `OpenAPIBuilder` - Document assembly

**Integration**: Schemas reflect validation constraints (min/max, patterns, enums)

**Design Decision**: Same derive macro generates both validation AND schema

### Browser Validation (domainstack-wasm)

**Purpose**: Run identical validation in browser via WebAssembly

**Key Features**:
- Zero drift - same Rust code, compiled to WASM
- Type registry for runtime type dispatch
- JSON-based API for JavaScript interop
- ~60KB bundle size

**API**:
```javascript
const validator = createValidator();
const result = validator.validate('User', jsonString);
```

**Design Decision**: Same validation errors in browser and server

### Code Generation CLI (domainstack-cli)

**Purpose**: Generate TypeScript/Zod schemas from Rust types

**Capabilities**:
- Parse Rust source files for `#[derive(Validate)]`
- Generate Zod schemas with matching constraints
- TypeScript type generation

**Usage**:
```bash
domainstack-cli generate --input src/ --output schemas.ts
```

**Design Decision**: Source-level parsing, not runtime reflection

## Data Flow

### 1. Domain Type Definition

```rust
#[derive(Validate)]
struct User {
    #[validate(length(min = 1, max = 50))]
    name: String,
}
```

### 2. Macro Expansion (Compile Time)

The derive macro generates:

```rust
impl Validate for User {
    fn validate(&self) -> Result<(), ValidationError> {
        let mut err = ValidationError::new();
        
        let rule = domainstack::rules::min_len(1)
            .and(domainstack::rules::max_len(50));
        if let Err(e) = domainstack::validate("name", self.name.as_str(), &rule) {
            err.extend(e);
        }
        
        if err.is_empty() { Ok(()) } else { Err(err) }
    }
}
```

### 3. Validation Execution (Runtime)

```rust
let user = User { name: "".to_string() };
let result = user.validate();
// Err(ValidationError {
//     violations: vec![
//         Violation {
//             path: Path("name"),
//             code: "min_length",
//             message: "Must be at least 1 characters",
//             meta: Meta { min: "1" }
//         }
//     ]
// })
```

### 4. HTTP Conversion

```rust
result.map_err(|e| e.into_envelope_error())?;
// Error {
//     code: Code::Validation,
//     status: 400,
//     message: "Validation failed: Must be at least 1 characters",
//     details: Some({ "fields": { "name": [...] } })
// }
```

### 5. JSON Serialization

```json
{
  "code": "VALIDATION",
  "status": 400,
  "message": "Validation failed: Must be at least 1 characters",
  "details": {
    "fields": {
      "name": [
        {
          "code": "min_length",
          "message": "Must be at least 1 characters",
          "meta": {"min": "1"}
        }
      ]
    }
  }
}
```

## Path Construction

### Structured Path API

Paths are built using a fluent API, never string formatting:

```rust
// Simple field
Path::root().field("email")                    // "email"

// Nested fields
Path::root()
    .field("guest")
    .field("email")
    .field("value")                            // "guest.email.value"

// Collection indices
Path::root()
    .field("rooms")
    .index(0)
    .field("adults")                           // "rooms[0].adults"
```

**Why Structured?**
- Type-safe construction
- Consistent formatting
- Easy to transform (`.prefixed()`)
- No string interpolation errors

## Rule Composition

### Rule<T> Design

```rust
pub struct Rule<T: ?Sized + 'static> {
    check: Box<dyn Fn(&T) -> Result<(), Violation> + 'static>,
}
```

**Key Design Decisions**:
- `T: ?Sized` - Supports both `String` and `str`
- `'static` - Rules can be stored and reused
- Boxed function - Enables composition
- Type parameter - Ensures type safety

### Composition Pattern

```rust
impl<T> Rule<T> {
    pub fn and(self, other: Rule<T>) -> Rule<T> { ... }
    pub fn or(self, other: Rule<T>) -> Rule<T> { ... }
    pub fn when(self, predicate: impl Fn(&T) -> bool) -> Rule<T> { ... }
}
```

**Example**:
```rust
let rule = min_len(5)          // Rule<str>
    .and(max_len(255))         // Rule<str>
    .and(email());             // Rule<str>
```

All rules type-check at compile time!

## Error Accumulation

### ValidationError Pattern

```rust
let mut err = ValidationError::new();

// Collect multiple violations
if let Err(e) = validate_field_1() {
    err.extend(e);
}
if let Err(e) = validate_field_2() {
    err.extend(e);
}

// Return all or nothing
if err.is_empty() { Ok(()) } else { Err(err) }
```

**Benefits**:
- Reports all errors at once (not just first failure)
- Better UX - users see all problems
- Efficient - single validation pass

### Nested Error Prefixing

```rust
// Validate nested struct
if let Err(e) = self.email.validate() {
    err.merge_prefixed("email", e);
}
// Errors from email become "email.value", "email.domain", etc.
```

The `.prefixed()` method transforms all error paths.

## Proc Macro Implementation

### Attribute Parsing

Uses `syn` to parse attributes:

```rust
#[validate(length(min = 1, max = 50))]
```

Parsed into:
```rust
ValidationRule::Length {
    min: Some(1),
    max: Some(50),
    code: None,
    message: None,
}
```

### Code Generation

Uses `quote!` macro to generate implementation:

```rust
quote! {
    impl domain stack::Validate for #struct_name {
        fn validate(&self) -> Result<(), domainstack::ValidationError> {
            let mut err = domainstack::ValidationError::new();
            #(#field_validations)*
            if err.is_empty() { Ok(()) } else { Err(err) }
        }
    }
}
```

## Design Decisions

### 1. Framework-Agnostic Core

**Decision**: Core library has zero web dependencies  
**Rationale**: Domain logic should be independent of HTTP framework  
**Benefit**: Works with Axum, Actix, Warp, or any other framework

### 2. Separate Integration Crates

**Decision**: HTTP integration in separate crate (domainstack-envelope)  
**Rationale**: Users can choose their integration layer  
**Benefit**: Core remains lightweight, integrations are opt-in

### 3. Derive Macro Optional

**Decision**: Derive macro is a feature flag  
**Rationale**: Some users prefer manual control  
**Benefit**: Minimal dependencies for simple use cases

### 4. Structured Paths Over Strings

**Decision**: Path is a type, not a String  
**Rationale**: Type-safe construction, consistent formatting  
**Benefit**: No string interpolation bugs, easy to transform

### 5. Reference Rules (&Rule<T>)

**Decision**: validate() accepts `&Rule<T>` not `Rule<T>`  
**Rationale**: Better error spans in generated code  
**Benefit**: Compiler errors point to actual validation rules

## Performance Characteristics

### Validation Cost

- **String rules**: O(n) where n = string length
- **Numeric rules**: O(1) comparisons
- **Nested validation**: Sum of child validation costs
- **Collection validation**: O(n × validation_cost) where n = collection size

### Memory Allocation

- ValidationError uses `Vec<Violation>` - grows as needed
- Path segments allocated per error
- Meta fields allocated per violation
- Zero-copy where possible (borrows data)

### Compile-Time Optimization

- Derive macro generates specialized code per type
- No runtime reflection
- Rules are inlined where possible
- Type checking at compile time

## See Also

- [Core Concepts]./CORE_CONCEPTS.md - Foundation principles
- [Rules Reference]./RULES.md - All validation rules
- [OpenAPI Schema]./OPENAPI_SCHEMA.md - Schema generation guide
- [WASM Validation]./WASM_VALIDATION.md - Browser validation guide
- [Source Code]../domainstack/ - Implementation