# rok-validate-macros

> Proc-macros for rok-validate: `#[derive(Validate)]`, `#[derive(Filterable)]`, and `#[derive(Sortable)]`.

Part of the [Rok Framework](https://rok.rs) — a full-stack Rust web framework built on Axum 0.8 and SQLx 0.8.

[![crates.io](https://img.shields.io/crates/v/rok-validate-macros)](https://crates.io/crates/rok-validate-macros) [![docs.rs](https://img.shields.io/docsrs/rok-validate-macros)](https://docs.rs/rok-validate-macros) [![MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

## Features

- `#[derive(Validate)]` expands per-field `#[validate(...)]` attributes into a `Validatable::validate()` implementation
- Supports all built-in rule keywords: `required`, `optional`, `email`, `url`, `alpha`, `numeric`, `min`, `max`, `regex`, `confirmed`, `unique`, `exists`
- `#[derive(Filterable)]` generates a `apply_filters(qb)` method for type-safe query parameter filtering
- `#[derive(Sortable)]` generates an `apply_sort(qb)` method with an allowlist of sortable columns
- Compile-time validation of rule syntax — unknown rules are caught before runtime
- Cross-field rules (`confirmed = "other_field"`) resolved at compile time to ensure the referenced field exists

## Installation

```toml
[dependencies]
rok-validate-macros = "0.2"
```

> This crate is re-exported by `rok-validate`. Prefer adding `rok-validate` directly:
> ```toml
> rok-validate = { version = "0.2", features = ["axum"] }
> ```

## Quick Start

```rust
use rok_validate_macros::{Validate, Filterable, Sortable};
use serde::Deserialize;

#[derive(Debug, Deserialize, Validate)]
pub struct UpdateProfileRequest {
    #[validate(required, min = 2, max = 100)]
    pub display_name: String,

    #[validate(optional, url)]
    pub website: Option<String>,

    #[validate(optional, regex = r"^\+?[0-9\s\-]{7,20}$")]
    pub phone: Option<String>,
}

#[derive(Deserialize, Filterable)]
pub struct ArticleFilters {
    #[filter(column = "author_id")]
    pub author_id: Option<i64>,

    #[filter(column = "published")]
    pub published: Option<bool>,

    #[filter(column = "title", op = "ilike")]
    pub title_contains: Option<String>,
}

#[derive(Deserialize, Sortable)]
pub struct ArticleSort {
    #[sort(allow = ["title", "published_at", "view_count"])]
    pub sort:      Option<String>,
    pub direction: Option<String>,
}
```

## Core API

### #[derive(Validate)] expansion

```rust
// Input:
#[derive(Validate)]
pub struct SignupForm {
    #[validate(required, email)]
    pub email: String,

    #[validate(required, min = 8)]
    pub password: String,

    #[validate(required, confirmed = "password")]
    pub password_confirmation: String,
}

// Generated (illustrative):
impl rok_validate::Validatable for SignupForm {
    async fn validate(&self) -> Result<(), rok_validate::ValidationErrors> {
        let mut errors = rok_validate::ValidationErrors::new();
        rok_validate::rules::required(&self.email,    "email",    &mut errors);
        rok_validate::rules::email(&self.email,       "email",    &mut errors);
        rok_validate::rules::required(&self.password, "password", &mut errors);
        rok_validate::rules::min_len(&self.password,  8, "password", &mut errors);
        rok_validate::rules::confirmed(
            &self.password_confirmation, &self.password,
            "password_confirmation", &mut errors,
        );
        if errors.is_empty() { Ok(()) } else { Err(errors) }
    }
}
```

### #[derive(Filterable)] expansion

```rust
// Generated apply_filters method signature:
impl ArticleFilters {
    pub fn apply_filters<M>(&self, qb: QueryBuilder<M>) -> QueryBuilder<M> {
        let mut qb = qb;
        if let Some(v) = &self.author_id        { qb = qb.filter("author_id", *v); }
        if let Some(v) = &self.published         { qb = qb.filter("published", *v); }
        if let Some(v) = &self.title_contains    { qb = qb.ilike("title", v); }
        qb
    }
}
```

## Integration

`rok-validate-macros` is consumed by `rok-validate` and re-exported through it. You access these macros via `use rok_validate::{Validate, Filterable, Sortable}` in application code. The generated `Validatable` implementation is picked up by the `Valid<T>` Axum extractor automatically.

```rust
// All three derives available through rok-validate:
use rok_validate::{Validate, Filterable, Sortable, Valid};
```

## License

MIT
