use serde_json::Value;
use crate::{
error::{FraiseQLError, Result, ValidationFieldError},
schema::CompiledSchema,
validation::{LengthCheck, ValidationRule, check_length},
};
#[derive(Debug, Clone, Default)]
pub struct ValidationErrorCollection {
pub errors: Vec<ValidationFieldError>,
}
impl ValidationErrorCollection {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_error(&mut self, error: ValidationFieldError) {
self.errors.push(error);
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.errors.is_empty()
}
#[must_use]
pub const fn len(&self) -> usize {
self.errors.len()
}
#[must_use]
pub fn to_error(&self) -> FraiseQLError {
if self.errors.is_empty() {
FraiseQLError::validation("No validation errors")
} else if self.errors.len() == 1 {
let err = &self.errors[0];
FraiseQLError::Validation {
message: err.to_string(),
path: Some(err.field.clone()),
}
} else {
let messages: Vec<String> = self.errors.iter().map(|e| e.to_string()).collect();
FraiseQLError::Validation {
message: format!("Multiple validation errors: {}", messages.join("; ")),
path: None,
}
}
}
}
pub fn validate_custom_scalar_from_schema(
value: &Value,
scalar_type_name: &str,
schema: &CompiledSchema,
) -> Result<()> {
if schema.custom_scalars.exists(scalar_type_name) {
schema.custom_scalars.validate(scalar_type_name, value)
} else {
Ok(())
}
}
pub fn validate_input(value: &Value, field_path: &str, rules: &[ValidationRule]) -> Result<()> {
let mut errors = ValidationErrorCollection::new();
match value {
Value::String(s) => {
for rule in rules {
if let Err(field_err) = check_string_field(s, field_path, rule) {
errors.add_error(field_err);
}
}
},
Value::Null => {
for rule in rules {
if rule.is_required() {
errors.add_error(ValidationFieldError::new(
field_path,
"required",
"Field is required",
));
}
}
},
_ => {
},
}
if errors.is_empty() {
Ok(())
} else {
Err(errors.to_error())
}
}
pub(crate) fn check_string_field(
value: &str,
field_path: &str,
rule: &ValidationRule,
) -> std::result::Result<(), ValidationFieldError> {
match rule {
ValidationRule::Required if value.is_empty() => {
Err(ValidationFieldError::new(field_path, "required", "Field is required"))
},
ValidationRule::Pattern { pattern, message } if !pattern.is_match(value) => {
let msg = message.clone().unwrap_or_else(|| "Pattern mismatch".to_string());
Err(ValidationFieldError::new(field_path, "pattern", msg))
},
ValidationRule::Length { min, max } => match check_length(value, *min, *max) {
LengthCheck::Ok => Ok(()),
LengthCheck::TooShort { min, actual } => Err(ValidationFieldError::new(
field_path,
"length",
format!("Length must be at least {min} characters, got {actual}"),
)),
LengthCheck::TooLong { max, actual } => Err(ValidationFieldError::new(
field_path,
"length",
format!("Length must be at most {max} characters, got {actual}"),
)),
},
ValidationRule::Enum { values } if !values.iter().any(|v| v == value) => {
Err(ValidationFieldError::new(
field_path,
"enum",
format!("Must be one of: {}", values.join(", ")),
))
},
_ => Ok(()),
}
}