tideway 0.7.17

A batteries-included Rust web framework built on Axum for building SaaS applications quickly
Documentation
# Request Validation

Tideway provides comprehensive request validation using the `validator` crate with custom validators and type-safe extractors.

## Overview

Request validation ensures that incoming data meets your application's requirements before it reaches your handlers. Tideway provides:

- **Validated Extractors**: Automatic validation when extracting request data
- **Custom Validators**: Domain-specific validation rules
- **Error Context**: Detailed validation error messages

## Basic Usage

### ValidatedJson

Validate JSON request bodies:

```rust
use tideway::validation::ValidatedJson;
use validator::Validate;
use serde::Deserialize;

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

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

    #[validate(range(min = 18, max = 100))]
    age: Option<u32>,
}

async fn create_user(
    ValidatedJson(req): ValidatedJson<CreateUserRequest>
) -> tideway::Result<axum::Json<serde_json::Value>> {
    // req is guaranteed to be valid
    Ok(axum::Json(serde_json::json!({"status": "created"})))
}
```

### ValidatedQuery

Validate query parameters:

```rust
use tideway::validation::ValidatedQuery;
use validator::Validate;
use serde::Deserialize;

#[derive(Deserialize, Validate)]
struct SearchQuery {
    #[validate(length(min = 1, max = 100))]
    q: String,

    #[validate(range(min = 1, max = 100))]
    limit: Option<u32>,

    #[validate(custom = "tideway::validation::validate_uuid")]
    user_id: Option<String>,
}

async fn search(
    ValidatedQuery(query): ValidatedQuery<SearchQuery>
) -> tideway::Result<axum::Json<serde_json::Value>> {
    // query is guaranteed to be valid
    Ok(axum::Json(serde_json::json!({"results": []})))
}
```

### ValidatedForm

Validate form data:

```rust
use tideway::validation::{ValidatedForm, validate_form};
use validator::Validate;
use serde::Deserialize;

#[derive(Deserialize, Validate)]
struct ContactForm {
    #[validate(email)]
    email: String,

    #[validate(length(min = 10, max = 500))]
    message: String,
}

async fn submit_contact(
    form: axum::extract::Form<ContactForm>
) -> tideway::Result<axum::Json<serde_json::Value>> {
    let ValidatedForm(data) = validate_form(form)?;
    // data is guaranteed to be valid
    Ok(axum::Json(serde_json::json!({"status": "sent"})))
}
```

## Custom Validators

Tideway provides several custom validators for common use cases:

### UUID Validation

```rust
use tideway::validation::validate_uuid;

#[derive(Validate)]
struct Request {
    #[validate(custom = "validate_uuid")]
    id: String,
}
```

### Slug Validation

Validates lowercase alphanumeric strings with hyphens and underscores:

```rust
use tideway::validation::validate_slug;

#[derive(Validate)]
struct Request {
    #[validate(custom = "validate_slug")]
    slug: String, // e.g., "my-awesome-slug_123"
}
```

### Phone Validation

Supports E.164 format (+1234567890) and 10-digit US format:

```rust
use tideway::validation::validate_phone;

#[derive(Validate)]
struct Request {
    #[validate(custom = "validate_phone")]
    phone: String, // e.g., "+1234567890" or "1234567890"
}
```

### JSON String Validation

Validates that a string contains valid JSON:

```rust
use tideway::validation::validate_json_string;

#[derive(Validate)]
struct Request {
    #[validate(custom = "validate_json_string")]
    config: String, // Must be valid JSON
}
```

### Duration Validation

Validates duration strings like "30s", "5m", "1h", "2d":

```rust
use tideway::validation::validate_duration;

#[derive(Validate)]
struct Request {
    #[validate(custom = "validate_duration")]
    timeout: String, // e.g., "30s", "5m", "1h", "2d"
}
```

## Built-in Validators

The `validator` crate provides many built-in validators:

```rust
#[derive(Validate)]
struct UserRequest {
    #[validate(email)]
    email: String,

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

    #[validate(length(min = 8, max = 64))]
    password: String,

    #[validate(range(min = 18, max = 100))]
    age: u32,

    #[validate(regex = "PHONE_REGEX")]
    phone: String,

    #[validate(contains = "@example.com")]
    company_email: String,
}
```

## Error Handling

Validation errors automatically return detailed error responses:

```json
{
  "error": "Bad request: Validation failed",
  "error_id": "550e8400-e29b-41d4-a716-446655440000",
  "field_errors": {
    "email": ["must be a valid email"],
    "password": ["must be at least 8 characters"],
    "age": ["must be between 18 and 100"]
  }
}
```

Access field errors in your error handler:

```rust
use tideway::{ErrorContext, TidewayError};

let context = ErrorContext::new()
    .with_field_error("email", "must be a valid email")
    .with_field_error("password", "must be at least 8 characters");

let error = TidewayError::bad_request("Validation failed")
    .with_context(context);
```

## Advanced Usage

### Nested Validation

Validate nested structures:

```rust
#[derive(Deserialize, Validate)]
struct Address {
    #[validate(length(min = 1))]
    street: String,

    #[validate(length(min = 1))]
    city: String,

    #[validate(length(min = 5, max = 10))]
    zip_code: String,
}

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

    #[validate]
    address: Address, // Nested validation
}
```

### Conditional Validation

Use custom validation logic:

```rust
use validator::ValidationError;

fn validate_password_strength(password: &str) -> Result<(), ValidationError> {
    if password.len() < 8 {
        return Err(ValidationError::new("password_strength"));
    }

    if !password.chars().any(|c| c.is_uppercase()) {
        return Err(ValidationError::new("password_strength"));
    }

    Ok(())
}

#[derive(Validate)]
struct Request {
    #[validate(custom = "validate_password_strength")]
    password: String,
}
```

## Best Practices

1. **Validate Early**: Use validated extractors to catch errors before business logic
2. **Custom Validators**: Create domain-specific validators for common patterns
3. **Error Messages**: Provide clear, actionable error messages
4. **Field Errors**: Use field-specific errors for better UX
5. **Nested Structures**: Validate nested data structures for complete type safety

## See Also

- [Validator Crate Documentation]https://docs.rs/validator/
- [Error Handling Guide]./error_handling.md
- [Request Handling Examples]../examples/validation_example.rs