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 localize<T: Translator>(&self, translator: &T) -> Self {
149 let fields = self
150 .fields
151 .iter()
152 .map(|f| {
153 let message = translator
154 .translate(&f.code, &f.field, f.params.as_ref())
155 .unwrap_or_else(|| f.message.clone());
156
157 FieldError {
158 field: f.field.clone(),
159 code: f.code.clone(),
160 message,
161 params: f.params.clone(),
162 }
163 })
164 .collect();
165
166 Self {
167 fields,
168 message: self.message.clone(),
169 }
170 }
171}
172
173impl fmt::Display for ValidationError {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 write!(f, "{}: {} field error(s)", self.message, self.fields.len())
176 }
177}
178
179impl std::error::Error for ValidationError {}
180
181impl Serialize for ValidationError {
182 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183 where
184 S: serde::Serializer,
185 {
186 let wrapper = ErrorWrapper {
187 error: ErrorBody {
188 error_type: "validation_error".to_string(),
189 message: self.message.clone(),
190 fields: self.fields.clone(),
191 },
192 };
193 wrapper.serialize(serializer)
194 }
195}
196
197impl<'de> Deserialize<'de> for ValidationError {
198 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
199 where
200 D: serde::Deserializer<'de>,
201 {
202 let wrapper = ErrorWrapper::deserialize(deserializer)?;
203 Ok(Self {
204 fields: wrapper.error.fields,
205 message: wrapper.error.message,
206 })
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn field_error_creation() {
216 let error = FieldError::new("email", "email", "Invalid email format");
217 assert_eq!(error.field, "email");
218 assert_eq!(error.code, "email");
219 assert_eq!(error.message, "Invalid email format");
220 assert!(error.params.is_none());
221 }
222
223 #[test]
224 fn validation_error_serialization() {
225 let error = ValidationError::new(vec![FieldError::new(
226 "email",
227 "email",
228 "Invalid email format",
229 )]);
230
231 let json = serde_json::to_value(&error).unwrap();
232
233 assert_eq!(json["error"]["type"], "validation_error");
234 assert_eq!(json["error"]["message"], "Validation failed");
235 assert_eq!(json["error"]["fields"][0]["field"], "email");
236 }
237
238 #[test]
239 fn validation_error_display() {
240 let error = ValidationError::new(vec![
241 FieldError::new("email", "email", "Invalid email"),
242 FieldError::new("age", "range", "Out of range"),
243 ]);
244
245 assert_eq!(error.to_string(), "Validation failed: 2 field error(s)");
246 }
247}