1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use std::fmt;
7
8pub trait Translator {
10 fn translate(
18 &self,
19 code: &str,
20 field: &str,
21 params: Option<&HashMap<String, serde_json::Value>>,
22 ) -> Option<String>;
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct FieldError {
28 pub field: String,
30 pub code: String,
32 pub message: String,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub params: Option<HashMap<String, serde_json::Value>>,
37}
38
39impl FieldError {
40 pub fn new(
42 field: impl Into<String>,
43 code: impl Into<String>,
44 message: impl Into<String>,
45 ) -> Self {
46 Self {
47 field: field.into(),
48 code: code.into(),
49 message: message.into(),
50 params: None,
51 }
52 }
53
54 pub fn with_params(
56 field: impl Into<String>,
57 code: impl Into<String>,
58 message: impl Into<String>,
59 params: HashMap<String, serde_json::Value>,
60 ) -> Self {
61 Self {
62 field: field.into(),
63 code: code.into(),
64 message: message.into(),
65 params: Some(params),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72struct ErrorBody {
73 #[serde(rename = "type")]
74 error_type: String,
75 message: String,
76 fields: Vec<FieldError>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81struct ErrorWrapper {
82 error: ErrorBody,
83}
84
85#[derive(Debug, Clone)]
99pub struct ValidationError {
100 pub fields: Vec<FieldError>,
102 pub message: String,
104}
105
106impl ValidationError {
107 pub fn new(fields: Vec<FieldError>) -> Self {
109 Self {
110 fields,
111 message: "Validation failed".to_string(),
112 }
113 }
114
115 pub fn with_message(fields: Vec<FieldError>, message: impl Into<String>) -> Self {
117 Self {
118 fields,
119 message: message.into(),
120 }
121 }
122
123 pub fn field(
125 field: impl Into<String>,
126 code: impl Into<String>,
127 message: impl Into<String>,
128 ) -> Self {
129 Self::new(vec![FieldError::new(field, code, message)])
130 }
131
132 pub fn is_empty(&self) -> bool {
134 self.fields.is_empty()
135 }
136
137 pub fn len(&self) -> usize {
139 self.fields.len()
140 }
141
142 pub fn add(&mut self, error: FieldError) {
144 self.fields.push(error);
145 }
146
147 pub fn from_validator_errors(errors: validator::ValidationErrors) -> Self {
149 let mut field_errors = Vec::new();
150
151 for (field, error_kinds) in errors.field_errors() {
152 for error in error_kinds {
153 let code = error.code.to_string();
154 let message = error
155 .message
156 .as_ref()
157 .map(|m| m.to_string())
158 .unwrap_or_else(|| format!("Validation failed for field '{}'", field));
159
160 let params = if error.params.is_empty() {
161 None
162 } else {
163 let mut map = HashMap::new();
164 for (key, value) in &error.params {
165 if let Ok(json_value) = serde_json::to_value(value) {
166 map.insert(key.to_string(), json_value);
167 }
168 }
169 Some(map)
170 };
171
172 field_errors.push(FieldError {
173 field: field.to_string(),
174 code,
175 message,
176 params,
177 });
178 }
179 }
180
181 Self::new(field_errors)
182 }
183
184 pub fn localize<T: Translator>(&self, translator: &T) -> Self {
186 let fields = self
187 .fields
188 .iter()
189 .map(|f| {
190 let message = translator
191 .translate(&f.code, &f.field, f.params.as_ref())
192 .unwrap_or_else(|| f.message.clone());
193
194 FieldError {
195 field: f.field.clone(),
196 code: f.code.clone(),
197 message,
198 params: f.params.clone(),
199 }
200 })
201 .collect();
202
203 Self {
204 fields,
205 message: self.message.clone(),
206 }
207 }
208}
209
210impl fmt::Display for ValidationError {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 write!(f, "{}: {} field error(s)", self.message, self.fields.len())
213 }
214}
215
216impl std::error::Error for ValidationError {}
217
218impl Serialize for ValidationError {
219 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220 where
221 S: serde::Serializer,
222 {
223 let wrapper = ErrorWrapper {
224 error: ErrorBody {
225 error_type: "validation_error".to_string(),
226 message: self.message.clone(),
227 fields: self.fields.clone(),
228 },
229 };
230 wrapper.serialize(serializer)
231 }
232}
233
234impl<'de> Deserialize<'de> for ValidationError {
235 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
236 where
237 D: serde::Deserializer<'de>,
238 {
239 let wrapper = ErrorWrapper::deserialize(deserializer)?;
240 Ok(Self {
241 fields: wrapper.error.fields,
242 message: wrapper.error.message,
243 })
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn field_error_creation() {
253 let error = FieldError::new("email", "email", "Invalid email format");
254 assert_eq!(error.field, "email");
255 assert_eq!(error.code, "email");
256 assert_eq!(error.message, "Invalid email format");
257 assert!(error.params.is_none());
258 }
259
260 #[test]
261 fn validation_error_serialization() {
262 let error = ValidationError::new(vec![FieldError::new(
263 "email",
264 "email",
265 "Invalid email format",
266 )]);
267
268 let json = serde_json::to_value(&error).unwrap();
269
270 assert_eq!(json["error"]["type"], "validation_error");
271 assert_eq!(json["error"]["message"], "Validation failed");
272 assert_eq!(json["error"]["fields"][0]["field"], "email");
273 }
274
275 #[test]
276 fn validation_error_display() {
277 let error = ValidationError::new(vec![
278 FieldError::new("email", "email", "Invalid email"),
279 FieldError::new("age", "range", "Out of range"),
280 ]);
281
282 assert_eq!(error.to_string(), "Validation failed: 2 field error(s)");
283 }
284}