kegani 0.1.0

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Validation extractors for Kegani
//!
//! Provides extractors for validated form and query data.

use actix_web::{FromRequest, HttpRequest, dev::Payload};
use serde::de::DeserializeOwned;
use std::future::{ready, Ready};
use crate::validation::validators::ValidationError;

/// Validated form extractor
///
/// Extracts and validates form data.
///
/// # Example
/// ```ignore
/// #[post("/users")]
/// async fn create_user(
///     ValidatedForm(payload): ValidatedForm<CreateUser>,
/// ) -> Result<Json<User>, AppError> {
///     // payload is validated
/// }
/// ```
#[derive(Debug)]
pub struct ValidatedForm<T>(pub T);

impl<T: DeserializeOwned + Validate> FromRequest for ValidatedForm<T> {
    type Error = actix_web::Error;
    type Future = Ready<Result<Self, Self::Error>>;

    async fn from_request(req: &HttpRequest, payload: &mut Payload) -> Result<Self, Self::Error> {
        let form = actix_web::web::Form::<T>::from_request(req, payload).await?;

        // Validate the form
        let inner = form.into_inner();
        inner.validate().map(ValidatedForm).map_err(|_| {
            actix_web::error::ErrorBadRequest("Validation failed")
        })
    }
}

/// Validated query extractor
///
/// Extracts and validates query parameters.
///
/// # Example
/// ```ignore
/// #[get("/users")]
/// async fn list_users(
///     ValidatedQuery(params): ValidatedQuery<Pagination>,
/// ) -> Result<Json<Vec<User>>, AppError> {
///     // params are validated
/// }
/// ```
#[derive(Debug)]
pub struct ValidatedQuery<T>(pub T);

impl<T: DeserializeOwned + Validate> FromRequest for ValidatedQuery<T> {
    type Error = actix_web::Error;
    type Future = Ready<Result<Self, Self::Error>>;

    async fn from_request(req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
        // Parse query string
        let query_string = req.query_string();
        let result: Result<T, _> = serde_json::from_str::<serde_json::Value>(&format!("{{{}}}", query_string))
            .and_then(|v| serde_json::from_value(v));

        result
            .map(|data| {
                let validated = T::validate_data(data)?;
                Ok(ValidatedQuery(validated))
            })
            .map_err(|_| actix_web::error::ErrorBadRequest("Invalid query parameters"))
    }
}

/// Trait for types that can be validated
pub trait Validate: Sized {
    /// Validate the data
    fn validate(self) -> Result<Self, Vec<ValidationError>>;

    /// Validate from JSON value
    fn validate_data(data: serde_json::Value) -> Result<Self, Vec<ValidationError>>
    where
        Self: for<'de> serde::Deserialize<'de>,
    {
        serde_json::from_value(data).map_err(|e| {
            vec![ValidationError::new("", &e.to_string())]
        })
    }
}

/// Validation helper for forms
pub fn validate_form<T: DeserializeOwned + Validate>(
    data: serde_json::Value,
) -> Result<T, Vec<ValidationError>> {
    let parsed = serde_json::from_value::<T>(data)
        .map_err(|e| vec![ValidationError::new("", &e.to_string())])?;

    parsed.validate()
}

/// Helper to create validation errors
pub fn validation_error(field: &str, message: &str) -> ValidationError {
    ValidationError::new(field, message)
}

/// Combine multiple validation results
pub fn combine_errors<A, B>(a: Result<A, Vec<ValidationError>>, b: Result<B, Vec<ValidationError>>) -> Result<(), Vec<ValidationError>> {
    match (a, b) {
        (Ok(_), Ok(_)) => Ok(()),
        (Ok(_), Err(e)) => Err(e),
        (Err(e), Ok(_)) => Err(e),
        (Err(mut e1), Err(e2)) => {
            e1.extend(e2);
            Err(e1)
        }
    }
}