use std::fmt;
use crate::{
error::{FraiseQLError, Result},
validation::rules::ValidationRule,
};
#[derive(Debug, Clone)]
pub struct CompositeError {
pub operator: CompositeOperator,
pub errors: Vec<String>,
pub field: String,
}
impl fmt::Display for CompositeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.operator)?;
if !self.errors.is_empty() {
write!(f, ": {}", self.errors.join("; "))?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CompositeOperator {
All,
Any,
Not,
Optional,
}
impl fmt::Display for CompositeOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::All => write!(f, "All validators must pass"),
Self::Any => write!(f, "At least one validator must pass"),
Self::Not => write!(f, "Validator must fail"),
Self::Optional => write!(f, "Optional validation"),
}
}
}
pub fn validate_all(
rules: &[ValidationRule],
field_value: &str,
field_name: &str,
is_present: bool,
) -> Result<()> {
let mut errors = Vec::new();
for rule in rules {
if let Err(e) = validate_single_rule(rule, field_value, field_name, is_present) {
errors.push(format!("{}", e));
}
}
if !errors.is_empty() {
return Err(FraiseQLError::Validation {
message: format!(
"All validators must pass for '{}': {}",
field_name,
errors.join("; ")
),
path: Some(field_name.to_string()),
});
}
Ok(())
}
pub fn validate_any(
rules: &[ValidationRule],
field_value: &str,
field_name: &str,
is_present: bool,
) -> Result<()> {
let mut errors = Vec::new();
let mut passed_count = 0;
for rule in rules {
match validate_single_rule(rule, field_value, field_name, is_present) {
Ok(()) => {
passed_count += 1;
},
Err(e) => {
errors.push(format!("{}", e));
},
}
}
if passed_count == 0 {
return Err(FraiseQLError::Validation {
message: format!(
"At least one validator must pass for '{}': {}",
field_name,
errors.join("; ")
),
path: Some(field_name.to_string()),
});
}
Ok(())
}
pub fn validate_not(
rule: &ValidationRule,
field_value: &str,
field_name: &str,
is_present: bool,
) -> Result<()> {
match validate_single_rule(rule, field_value, field_name, is_present) {
Ok(()) => Err(FraiseQLError::Validation {
message: format!("Validator for '{}' must fail but passed", field_name),
path: Some(field_name.to_string()),
}),
Err(_) => Ok(()), }
}
pub fn validate_optional(
rule: &ValidationRule,
field_value: &str,
field_name: &str,
is_present: bool,
) -> Result<()> {
if !is_present {
return Ok(());
}
validate_single_rule(rule, field_value, field_name, is_present)
}
pub(crate) fn validate_single_rule(
rule: &ValidationRule,
field_value: &str,
field_name: &str,
_is_present: bool,
) -> Result<()> {
match rule {
ValidationRule::Required => {
if field_value.is_empty() {
return Err(FraiseQLError::Validation {
message: format!("Field '{}' is required", field_name),
path: Some(field_name.to_string()),
});
}
Ok(())
},
ValidationRule::Pattern { pattern, message } => {
if !pattern.is_match(field_value) {
return Err(FraiseQLError::Validation {
message: message.clone().unwrap_or_else(|| {
format!("'{}' must match pattern: {}", field_name, pattern)
}),
path: Some(field_name.to_string()),
});
}
Ok(())
},
ValidationRule::Length { min, max } => {
let len = field_value.len();
if let Some(min_len) = min {
if len < *min_len {
return Err(FraiseQLError::Validation {
message: format!(
"'{}' must be at least {} characters",
field_name, min_len
),
path: Some(field_name.to_string()),
});
}
}
if let Some(max_len) = max {
if len > *max_len {
return Err(FraiseQLError::Validation {
message: format!("'{}' must be at most {} characters", field_name, max_len),
path: Some(field_name.to_string()),
});
}
}
Ok(())
},
ValidationRule::Enum { values } => {
if !values.contains(&field_value.to_string()) {
return Err(FraiseQLError::Validation {
message: format!("'{}' must be one of: {}", field_name, values.join(", ")),
path: Some(field_name.to_string()),
});
}
Ok(())
},
_ => Ok(()),
}
}