use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Clone, Default, Serialize)]
pub struct ValidationError {
errors: HashMap<String, Vec<String>>,
}
impl ValidationError {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, field: &str, message: impl Into<String>) {
self.errors
.entry(field.to_string())
.or_default()
.push(message.into());
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub fn has(&self, field: &str) -> bool {
self.errors.contains_key(field)
}
pub fn get(&self, field: &str) -> Option<&Vec<String>> {
self.errors.get(field)
}
pub fn first(&self, field: &str) -> Option<&String> {
self.errors.get(field).and_then(|v| v.first())
}
pub fn all(&self) -> &HashMap<String, Vec<String>> {
&self.errors
}
pub fn count(&self) -> usize {
self.errors.values().map(|v| v.len()).sum()
}
pub fn messages(&self) -> Vec<&String> {
self.errors.values().flatten().collect()
}
pub fn into_messages(self) -> HashMap<String, Vec<String>> {
self.errors
}
pub fn to_json(&self) -> serde_json::Value {
serde_json::json!({
"message": "The given data was invalid.",
"errors": self.errors
})
}
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let messages: Vec<String> = self
.errors
.iter()
.flat_map(|(field, msgs)| msgs.iter().map(move |m| format!("{field}: {m}")))
.collect();
write!(f, "{}", messages.join(", "))
}
}
impl std::error::Error for ValidationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_error_add() {
let mut errors = ValidationError::new();
errors.add("email", "The email field is required.");
errors.add("email", "The email must be a valid email address.");
errors.add("password", "The password must be at least 8 characters.");
assert!(!errors.is_empty());
assert!(errors.has("email"));
assert!(errors.has("password"));
assert!(!errors.has("name"));
assert_eq!(errors.count(), 3);
}
#[test]
fn test_validation_error_first() {
let mut errors = ValidationError::new();
errors.add("email", "First error");
errors.add("email", "Second error");
assert_eq!(errors.first("email"), Some(&"First error".to_string()));
assert_eq!(errors.first("name"), None);
}
#[test]
fn test_validation_error_to_json() {
let mut errors = ValidationError::new();
errors.add("email", "Required");
let json = errors.to_json();
assert!(json.get("message").is_some());
assert!(json.get("errors").is_some());
}
}