# ๐งช DSL vs Macros
Valida offers two distinct ways to define validation logic:
- ๐ง **Macro-Based (`#[Validatable]`)** โ declarative, concise, built for speed
- ๐ง **DSL-Based (`RulesBuilder`)** โ explicit, flexible, perfect for complex flows
Both approaches generate the same internal validation logic and support all Valida features: nested rules, custom validators, localization, and error output formatting.
---
## โ๏ธ DSL Style
The DSL builder uses explicit Rust syntax and is ideal for complex scenarios or when control over composition is needed:
```rust
pub struct UserValidator;
#[async_trait::async_trait]
impl IValidate<User, std::io::Error> for UserValidator {
fn rules(&self, mut builder: RulesBuilder<User, std::io::Error>) -> RulesBuilder<User, std::io::Error> {
builder
.field("email", |u| &u.email)
.trimmed().lowercased().email().build();
builder
.field("age", |u| &u.age)
.min(0).custom_async(MinAge(18)).build();
builder
.field("device", |u| &u.device)
.nested(DeviceValidator).build();
builder
}
}
```
# โ
Benefits of DSL
* Full visibility and control
* Works well with generics, dynamic dispatch
* Ideal for validators outside your data types (e.g. external modules)
---
# โก Macro Style
With `#[Validatable(error_type)]`, you can annotate your DTOs directly. This approach is cleaner and more declarative for typical use cases:
```rust
#[Validatable(std::io::Error)]
pub struct User {
#[validate(trimmed, email, min_length(5))]
pub email: String,
#[validate(min(0), custom_async(MinAge(18)))]
pub age: i32,
#[validate(nested(DeviceValidator))]
pub device: Device,
}
```
The macro automatically generates a validator named UserValidator.
# โ
Benefits of Macros
* Less boilerplate
* Inline rules improve readability
* Faster prototyping
* Encourages co-location with data definitions
---
# ๐จ When to Use DSL Over Macros
* When validator logic must live in separate modules
* When validator state depends on runtime configuration
* When rules require conditional logic
* When macro limitations interfere (e.g. repeated `#[Validatable]` in same scope)
---
# ๐ Combining Both
You can freely mix both styles in the same codebase. For example:
```rust
#[Validatable(std::io::Error)]
pub struct Profile { ... }
pub struct ContainerValidator;
impl IValidate<Container, std::io::Error> for ContainerValidator {
fn rules(...) { ... }
}
```