kegani 0.1.1

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Garde integration for declarative request validation
//!
//! Provides `validate()` and `validate_or_raise()` helpers for
//! request payloads validated at compile time via garde macros.
//!
//! Enable with: `kegani = { features = ["validation"] }`
//!
//! # Example
//!
//! ```ignore
//! use garde::Validate;
//! use serde::Deserialize;
//!
//! #[derive(Deserialize, Validate)]
//! pub struct CreateUser {
//!     #[garde(length(min = 3, max = 64))]
//!     pub name: String,
//!     #[garde(email)]
//!     pub email: String,
//!     #[garde(length(min = 8))]
//!     pub password: String,
//! }
//!
//! // In handler:
//! let user = validate_or_raise::<CreateUser>(payload)?;
//! ```

use crate::validation::validators::ValidationError;
use std::fmt::{Display, Formatter};
use serde::{Deserialize, Serialize};

/// Errors that can occur during garde validation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GardeError {
    pub fields: Vec<ValidationError>,
}

impl Display for GardeError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "validation failed: {}",
            self.fields.iter()
                .map(|e| format!("{}: {}", e.field, e.message))
                .collect::<Vec<_>>()
                .join("; ")
        )
    }
}

/// Validate a value using garde's Validate trait.
/// Returns `Ok(())` on success, or `Err(Vec<ValidationError>)` on failure.
///
/// Requires `T::Context: Default` which is satisfied by all structs using
/// standard garde rules without custom contexts.
///
/// # Example
/// ```ignore
/// let result = validate::<CreateUser>(&payload);
/// ```
pub fn validate<T>(value: &T) -> Result<(), Vec<ValidationError>>
where
    T: garde::Validate,
    <T as garde::Validate>::Context: Default,
{
    match value.validate() {
        Ok(()) => Ok(()),
        Err(report) => {
            let errors: Vec<ValidationError> = report
                .iter()
                .map(|item| {
                    let (path, error) = item;
                    let field = path.to_string();
                    ValidationError::new(&field, error.message())
                })
                .collect();
            Err(errors)
        }
    }
}

/// Validate and return the value, raising AppError on failure.
/// Returns the cloned value on success, or `crate::AppError::ValidationReport` on failure.
///
/// # Example
/// ```ignore
/// let user = validate_or_raise::<CreateUser>(payload)?;
/// ```
pub fn validate_or_raise<T>(value: T) -> crate::Result<T>
where
    T: garde::Validate + Clone,
    <T as garde::Validate>::Context: Default,
{
    match value.validate() {
        Ok(()) => Ok(value),
        Err(report) => {
            let errors: Vec<ValidationError> = report
                .iter()
                .map(|item| {
                    let (path, error) = item;
                    let field = path.to_string();
                    ValidationError::new(&field, error.message())
                })
                .collect();
            Err(crate::AppError::validation_report(errors))
        }
    }
}

// Re-export garde Validate trait for convenience
pub use garde::Validate;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validation_error_display() {
        let err = GardeError {
            fields: vec![
                ValidationError::new("email", "must be a valid email"),
                ValidationError::new("name", "too short"),
            ],
        };
        let display = format!("{}", err);
        assert!(display.contains("email"));
        assert!(display.contains("name"));
    }
}