ferro_rs/validation/
error.rs1use serde::Serialize;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Default, Serialize)]
8pub struct ValidationError {
9 errors: HashMap<String, Vec<String>>,
11}
12
13impl ValidationError {
14 pub fn new() -> Self {
16 Self::default()
17 }
18
19 pub fn add(&mut self, field: &str, message: impl Into<String>) {
21 self.errors
22 .entry(field.to_string())
23 .or_default()
24 .push(message.into());
25 }
26
27 pub fn is_empty(&self) -> bool {
29 self.errors.is_empty()
30 }
31
32 pub fn has(&self, field: &str) -> bool {
34 self.errors.contains_key(field)
35 }
36
37 pub fn get(&self, field: &str) -> Option<&Vec<String>> {
39 self.errors.get(field)
40 }
41
42 pub fn first(&self, field: &str) -> Option<&String> {
44 self.errors.get(field).and_then(|v| v.first())
45 }
46
47 pub fn all(&self) -> &HashMap<String, Vec<String>> {
49 &self.errors
50 }
51
52 pub fn count(&self) -> usize {
54 self.errors.values().map(|v| v.len()).sum()
55 }
56
57 pub fn messages(&self) -> Vec<&String> {
59 self.errors.values().flatten().collect()
60 }
61
62 pub fn into_messages(self) -> HashMap<String, Vec<String>> {
65 self.errors
66 }
67
68 pub fn to_json(&self) -> serde_json::Value {
70 serde_json::json!({
71 "message": "The given data was invalid.",
72 "errors": self.errors
73 })
74 }
75}
76
77impl std::fmt::Display for ValidationError {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 let messages: Vec<String> = self
80 .errors
81 .iter()
82 .flat_map(|(field, msgs)| msgs.iter().map(move |m| format!("{field}: {m}")))
83 .collect();
84 write!(f, "{}", messages.join(", "))
85 }
86}
87
88impl std::error::Error for ValidationError {}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_validation_error_add() {
96 let mut errors = ValidationError::new();
97 errors.add("email", "The email field is required.");
98 errors.add("email", "The email must be a valid email address.");
99 errors.add("password", "The password must be at least 8 characters.");
100
101 assert!(!errors.is_empty());
102 assert!(errors.has("email"));
103 assert!(errors.has("password"));
104 assert!(!errors.has("name"));
105 assert_eq!(errors.count(), 3);
106 }
107
108 #[test]
109 fn test_validation_error_first() {
110 let mut errors = ValidationError::new();
111 errors.add("email", "First error");
112 errors.add("email", "Second error");
113
114 assert_eq!(errors.first("email"), Some(&"First error".to_string()));
115 assert_eq!(errors.first("name"), None);
116 }
117
118 #[test]
119 fn test_validation_error_to_json() {
120 let mut errors = ValidationError::new();
121 errors.add("email", "Required");
122
123 let json = errors.to_json();
124 assert!(json.get("message").is_some());
125 assert!(json.get("errors").is_some());
126 }
127}