valida 1.1.2

Modular validation system for Rust with support for nested structures and localization.
Documentation
use crate::core::contract::{
    IValidate, IValidatorRule, IValidatorRuleCustom, IValidatorRuleCustomAsync, ValidatorFailure,
};
use crate::core::errors::{ValidationError, ValidationErrors};
use crate::core::field_builder::main::FieldBuilder;
use async_trait::async_trait;
use std::collections::HashMap;
use std::error::Error;
use std::marker::PhantomData;
use std::sync::Arc;

#[async_trait]
pub trait ValidateFieldAsync<T, E>: Send + Sync
where
    E: Error + Send + Sync + 'static,
{
    async fn validate_async(&self, dto: &T) -> Result<Vec<ValidationError>, E>;
}

pub struct FieldRules<T, V, E>
where
    E: Error + Send + Sync + 'static,
{
    pub field_name: &'static str,
    pub accessor: Arc<dyn Fn(&T) -> &V + Send + Sync>,
    pub rules: Vec<Box<dyn IValidatorRule<V>>>,
    pub rules_custom: Vec<Box<dyn IValidatorRuleCustom<V, E>>>,
    pub rules_custom_async: Vec<Box<dyn IValidatorRuleCustomAsync<V, E>>>,
}

#[async_trait]
impl<T, V, E> ValidateFieldAsync<T, E> for FieldRules<T, V, E>
where
    T: Sync,
    V: Send + Sync + 'static,
    E: Error + Send + Sync + 'static,
{
    async fn validate_async(&self, dto: &T) -> Result<Vec<ValidationError>, E> {
        let mut errors = vec![];
        let value = (self.accessor)(dto);

        for rule in &self.rules {
            if let Err(e) = rule.validate(value) {
                errors.push(e);
            }
        }

        for rule in &self.rules_custom {
            if let Some(e) = rule.validate(value)? {
                errors.push(e);
            }
        }

        for rule in &self.rules_custom_async {
            if let Some(e) = rule.validate(value).await? {
                errors.push(e);
            }
        }

        Ok(errors)
    }
}

pub struct RulesBuilder<T, E>
where
    E: Error + Send + Sync + 'static,
{
    pub fields: HashMap<String, Box<dyn ValidateFieldAsync<T, E>>>,
    pub nested: HashMap<String, Box<dyn IValidate<T, E>>>,
    pub _phantom: PhantomData<E>,
}

impl<T, E> RulesBuilder<T, E>
where
    T: Send + Sync + 'static,
    E: Error + Send + Sync + 'static,
{
    pub fn new() -> Self {
        Self {
            fields: HashMap::new(),
            nested: HashMap::new(),
            _phantom: Default::default(),
        }
    }

    pub fn field<TField>(
        &mut self,
        field_name: &'static str,
        accessor: impl Fn(&T) -> &TField + Send + Sync + 'static,
    ) -> FieldBuilder<'_, T, TField, E>
    where
        TField: Send + Sync + 'static,
    {
        let accessor = Arc::new(accessor);

        let rules = FieldRules {
            field_name,
            accessor,
            rules: vec![],
            rules_custom: vec![],
            rules_custom_async: vec![],
        };

        FieldBuilder {
            rules_builder: self,
            rules,
        }
    }

    pub async fn validate(&self, dto: &T) -> Result<ValidationErrors, E> {
        let mut result = ValidationErrors::default();

        for (field_name, field) in &self.fields {
            let field_errors = field.validate_async(dto).await?;
            for error in field_errors {
                result.add(vec![field_name.clone()], error);
            }
        }

        for (field_name, validator) in &self.nested {
            match validator.validate(dto).await {
                Ok(_) => {}
                Err(ValidatorFailure::Invalid(nested_errors)) => {
                    result.add_nested(vec![field_name.clone()], nested_errors);
                }
                Err(ValidatorFailure::System(e)) => {
                    return Err(e);
                }
            }
        }

        Ok(result)
    }
}

impl<T, E> Default for RulesBuilder<T, E>
where
    T: Send + Sync + 'static,
    E: Error + Send + Sync + 'static,
{
    fn default() -> Self {
        Self::new()
    }
}