# rok-validate

Request validation for Axum via `#[derive(Validate)]`. Rejects invalid payloads with
structured 422 JSON errors, and provides `Filterable`/`Sortable` for query parameters.

## Installation

```toml
[dependencies]
rok-validate = { version = "0.1", features = ["axum"] }
```

## Quick Start

```rust
use rok_validate::{Validate, Valid};
use serde::Deserialize;

#[derive(Debug, Deserialize, Validate)]
pub struct CreateUserRequest {
    #[validate(required, email)]
    pub email: String,

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

    #[validate(required, min = 2, max = 100)]
    pub name: String,
}

// In handler — rejects with 422 if invalid:
async fn create_user(
    Valid(req): Valid<CreateUserRequest>,
) -> impl IntoResponse {
    // req is guaranteed valid here
    StatusCode::CREATED
}
```

## Validation Rules

| Rule | Description |
|---|---|
| `required` | Field must be non-None and non-empty string |
| `optional` | Skip remaining rules if field is None/absent |
| `email` | Basic email format |
| `url` | Must start with http:// or https:// |
| `numeric` | Must parse as a finite number |
| `min = N` | String length ≥ N, or number ≥ N |
| `max = N` | String length ≤ N, or number ≤ N |
| `same = "field"` | Must equal another field's value |

## Error Response (422)

```json
{
  "message": "Validation failed",
  "errors": {
    "email": ["Must be a valid email address"],
    "password": ["Must be at least 8 characters"]
  }
}
```

## Sortable Query Parameters

```rust
#[derive(Deserialize, Sortable)]
pub struct UserSort {
    #[sort(allow = ["name", "created_at", "email"])]
    pub sort: Option<String>,
    pub direction: Option<String>,
}

// GET /users?sort=created_at&direction=desc
let param: SortParam = sort.into_sort_param()?;
User::query().order_by_param(param).get(&pool).await?;
```

## Filterable

```rust
#[derive(Deserialize, Filterable)]
pub struct UserFilters {
    #[filter(column = "email")]
    pub email: Option<String>,
    #[filter(column = "active")]
    pub active: Option<bool>,
}
```
