1use std::collections::HashMap;
4use std::fmt;
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8pub type ValidationResult<T> = Result<T, ValidationErrors>;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct ValidationError {
13 pub field: String,
15 pub message: String,
17 pub code: String,
19 pub context: Option<serde_json::Value>,
21}
22
23impl ValidationError {
24 pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
26 Self {
27 field: field.into(),
28 message: message.into(),
29 code: "validation_failed".to_string(),
30 context: None,
31 }
32 }
33
34 pub fn with_code(field: impl Into<String>, message: impl Into<String>, code: impl Into<String>) -> Self {
36 Self {
37 field: field.into(),
38 message: message.into(),
39 code: code.into(),
40 context: None,
41 }
42 }
43
44 pub fn with_context(field: impl Into<String>, message: impl Into<String>, context: serde_json::Value) -> Self {
46 Self {
47 field: field.into(),
48 message: message.into(),
49 code: "validation_failed".to_string(),
50 context: Some(context),
51 }
52 }
53
54 pub fn code(mut self, code: impl Into<String>) -> Self {
56 self.code = code.into();
57 self
58 }
59
60 pub fn context(mut self, context: serde_json::Value) -> Self {
62 self.context = Some(context);
63 self
64 }
65}
66
67impl fmt::Display for ValidationError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{}: {}", self.field, self.message)
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, Error)]
75pub struct ValidationErrors {
76 pub errors: HashMap<String, Vec<ValidationError>>,
78}
79
80impl ValidationErrors {
81 pub fn new() -> Self {
83 Self {
84 errors: HashMap::new(),
85 }
86 }
87
88 pub fn add(&mut self, error: ValidationError) {
90 self.errors
91 .entry(error.field.clone())
92 .or_default()
93 .push(error);
94 }
95
96 pub fn add_errors(&mut self, field: impl Into<String>, errors: Vec<ValidationError>) {
98 let field = field.into();
99 self.errors
100 .entry(field)
101 .or_default()
102 .extend(errors);
103 }
104
105 pub fn add_error(&mut self, field: impl Into<String>, message: impl Into<String>) {
107 let error = ValidationError::new(field.into(), message);
108 self.add(error);
109 }
110
111 pub fn is_empty(&self) -> bool {
113 self.errors.is_empty()
114 }
115
116 pub fn len(&self) -> usize {
118 self.errors.len()
119 }
120
121 pub fn total_errors(&self) -> usize {
123 self.errors.values().map(|v| v.len()).sum()
124 }
125
126 pub fn get_field_errors(&self, field: &str) -> Option<&Vec<ValidationError>> {
128 self.errors.get(field)
129 }
130
131 pub fn has_field_errors(&self, field: &str) -> bool {
133 self.errors.contains_key(field) && !self.errors[field].is_empty()
134 }
135
136 pub fn merge(&mut self, other: ValidationErrors) {
138 for (field, errors) in other.errors {
139 self.errors
140 .entry(field)
141 .or_default()
142 .extend(errors);
143 }
144 }
145
146 pub fn from_error(error: ValidationError) -> Self {
148 let mut errors = Self::new();
149 errors.add(error);
150 errors
151 }
152
153 pub fn to_json(&self) -> serde_json::Value {
155 serde_json::json!({
156 "error": {
157 "code": "validation_failed",
158 "message": "Validation failed",
159 "fields": self.errors
160 }
161 })
162 }
163}
164
165impl Default for ValidationErrors {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl fmt::Display for ValidationErrors {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 if self.errors.is_empty() {
174 write!(f, "No validation errors")
175 } else {
176 write!(f, "Validation failed for {} field(s):", self.errors.len())?;
177 for (field, field_errors) in &self.errors {
178 for error in field_errors {
179 write!(f, "\n {}: {}", field, error.message)?;
180 }
181 }
182 Ok(())
183 }
184 }
185}
186
187impl From<ValidationError> for ValidationErrors {
188 fn from(error: ValidationError) -> Self {
189 Self::from_error(error)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_validation_error_creation() {
199 let error = ValidationError::new("email", "Invalid email format");
200 assert_eq!(error.field, "email");
201 assert_eq!(error.message, "Invalid email format");
202 assert_eq!(error.code, "validation_failed");
203 assert!(error.context.is_none());
204 }
205
206 #[test]
207 fn test_validation_error_with_code() {
208 let error = ValidationError::with_code("age", "Must be positive", "positive_number");
209 assert_eq!(error.code, "positive_number");
210 }
211
212 #[test]
213 fn test_validation_errors_collection() {
214 let mut errors = ValidationErrors::new();
215
216 errors.add_error("email", "Invalid format");
217 errors.add_error("age", "Must be positive");
218 errors.add_error("email", "Already exists");
219
220 assert_eq!(errors.len(), 2); assert_eq!(errors.total_errors(), 3); assert!(errors.has_field_errors("email"));
223 assert!(errors.has_field_errors("age"));
224 assert!(!errors.has_field_errors("name"));
225
226 let email_errors = errors.get_field_errors("email").unwrap();
227 assert_eq!(email_errors.len(), 2);
228 }
229
230 #[test]
231 fn test_validation_errors_merge() {
232 let mut errors1 = ValidationErrors::new();
233 errors1.add_error("field1", "Error 1");
234
235 let mut errors2 = ValidationErrors::new();
236 errors2.add_error("field2", "Error 2");
237 errors2.add_error("field1", "Error 3");
238
239 errors1.merge(errors2);
240
241 assert_eq!(errors1.len(), 2);
242 assert_eq!(errors1.total_errors(), 3);
243 assert_eq!(errors1.get_field_errors("field1").unwrap().len(), 2);
244 }
245}