use serde_json::Value;
use crate::{
error::{FraiseQLError, Result, ValidationFieldError},
schema::CompiledSchema,
validation::ValidationRule,
};
#[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(FraiseQLError::Validation { message, .. }) =
validate_string_field(s, field_path, rule)
{
if let Some(field_err) = extract_field_error(&message) {
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 validate_string_field(
value: &str,
field_path: &str,
rule: &ValidationRule,
) -> Result<()> {
match rule {
ValidationRule::Required => {
if value.is_empty() {
return Err(FraiseQLError::Validation {
message: format!(
"Field validation failed: {}",
ValidationFieldError::new(field_path, "required", "Field is required")
),
path: Some(field_path.to_string()),
});
}
Ok(())
},
ValidationRule::Pattern { pattern, message } => {
if pattern.is_match(value) {
Ok(())
} else {
let msg = message.clone().unwrap_or_else(|| "Pattern mismatch".to_string());
Err(FraiseQLError::Validation {
message: format!(
"Field validation failed: {}",
ValidationFieldError::new(field_path, "pattern", msg)
),
path: Some(field_path.to_string()),
})
}
},
ValidationRule::Length { min, max } => {
let len = value.len();
let valid = if let Some(m) = min { len >= *m } else { true }
&& if let Some(m) = max { len <= *m } else { true };
if valid {
Ok(())
} else {
let msg = match (min, max) {
(Some(m), Some(x)) => format!("Length must be between {} and {}", m, x),
(Some(m), None) => format!("Length must be at least {}", m),
(None, Some(x)) => format!("Length must be at most {}", x),
(None, None) => "Length validation failed".to_string(),
};
Err(FraiseQLError::Validation {
message: format!(
"Field validation failed: {}",
ValidationFieldError::new(field_path, "length", msg)
),
path: Some(field_path.to_string()),
})
}
},
ValidationRule::Enum { values } => {
if values.contains(&value.to_string()) {
Ok(())
} else {
Err(FraiseQLError::Validation {
message: format!(
"Field validation failed: {}",
ValidationFieldError::new(
field_path,
"enum",
format!("Must be one of: {}", values.join(", "))
)
),
path: Some(field_path.to_string()),
})
}
},
_ => Ok(()), }
}
fn extract_field_error(message: &str) -> Option<ValidationFieldError> {
if message.contains("Field validation failed:") {
if let Some(field_start) = message.find("Field validation failed: ") {
let rest = &message[field_start + "Field validation failed: ".len()..];
if let Some(paren_start) = rest.find('(') {
if let Some(paren_end) = rest.find(')') {
let field = rest[..paren_start].trim().to_string();
let rule_type = rest[paren_start + 1..paren_end].to_string();
let msg_start = rest.find(": ").unwrap_or(0) + 2;
let message_text = rest[msg_start..].to_string();
return Some(ValidationFieldError::new(field, rule_type, message_text));
}
}
}
}
None
}