1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct FieldError {
10 pub field: String,
12 pub code: String,
14 pub message: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub params: Option<HashMap<String, serde_json::Value>>,
19}
20
21impl FieldError {
22 pub fn new(
24 field: impl Into<String>,
25 code: impl Into<String>,
26 message: impl Into<String>,
27 ) -> Self {
28 Self {
29 field: field.into(),
30 code: code.into(),
31 message: message.into(),
32 params: None,
33 }
34 }
35
36 pub fn with_params(
38 field: impl Into<String>,
39 code: impl Into<String>,
40 message: impl Into<String>,
41 params: HashMap<String, serde_json::Value>,
42 ) -> Self {
43 Self {
44 field: field.into(),
45 code: code.into(),
46 message: message.into(),
47 params: Some(params),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54struct ErrorBody {
55 #[serde(rename = "type")]
56 error_type: String,
57 message: String,
58 fields: Vec<FieldError>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63struct ErrorWrapper {
64 error: ErrorBody,
65}
66
67#[derive(Debug, Clone)]
81pub struct ValidationError {
82 pub fields: Vec<FieldError>,
84 pub message: String,
86}
87
88impl ValidationError {
89 pub fn new(fields: Vec<FieldError>) -> Self {
91 Self {
92 fields,
93 message: "Validation failed".to_string(),
94 }
95 }
96
97 pub fn with_message(fields: Vec<FieldError>, message: impl Into<String>) -> Self {
99 Self {
100 fields,
101 message: message.into(),
102 }
103 }
104
105 pub fn field(
107 field: impl Into<String>,
108 code: impl Into<String>,
109 message: impl Into<String>,
110 ) -> Self {
111 Self::new(vec![FieldError::new(field, code, message)])
112 }
113
114 pub fn is_empty(&self) -> bool {
116 self.fields.is_empty()
117 }
118
119 pub fn len(&self) -> usize {
121 self.fields.len()
122 }
123
124 pub fn add(&mut self, error: FieldError) {
126 self.fields.push(error);
127 }
128
129 pub fn from_validator_errors(errors: validator::ValidationErrors) -> Self {
131 let mut field_errors = Vec::new();
132
133 for (field, error_kinds) in errors.field_errors() {
134 for error in error_kinds {
135 let code = error.code.to_string();
136 let message = error
137 .message
138 .as_ref()
139 .map(|m| m.to_string())
140 .unwrap_or_else(|| format!("Validation failed for field '{}'", field));
141
142 let params = if error.params.is_empty() {
143 None
144 } else {
145 let mut map = HashMap::new();
146 for (key, value) in &error.params {
147 if let Ok(json_value) = serde_json::to_value(value) {
148 map.insert(key.to_string(), json_value);
149 }
150 }
151 Some(map)
152 };
153
154 field_errors.push(FieldError {
155 field: field.to_string(),
156 code,
157 message,
158 params,
159 });
160 }
161 }
162
163 Self::new(field_errors)
164 }
165}
166
167impl fmt::Display for ValidationError {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(f, "{}: {} field error(s)", self.message, self.fields.len())
170 }
171}
172
173impl std::error::Error for ValidationError {}
174
175impl Serialize for ValidationError {
176 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177 where
178 S: serde::Serializer,
179 {
180 let wrapper = ErrorWrapper {
181 error: ErrorBody {
182 error_type: "validation_error".to_string(),
183 message: self.message.clone(),
184 fields: self.fields.clone(),
185 },
186 };
187 wrapper.serialize(serializer)
188 }
189}
190
191impl<'de> Deserialize<'de> for ValidationError {
192 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
193 where
194 D: serde::Deserializer<'de>,
195 {
196 let wrapper = ErrorWrapper::deserialize(deserializer)?;
197 Ok(Self {
198 fields: wrapper.error.fields,
199 message: wrapper.error.message,
200 })
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn field_error_creation() {
210 let error = FieldError::new("email", "email", "Invalid email format");
211 assert_eq!(error.field, "email");
212 assert_eq!(error.code, "email");
213 assert_eq!(error.message, "Invalid email format");
214 assert!(error.params.is_none());
215 }
216
217 #[test]
218 fn validation_error_serialization() {
219 let error = ValidationError::new(vec![FieldError::new(
220 "email",
221 "email",
222 "Invalid email format",
223 )]);
224
225 let json = serde_json::to_value(&error).unwrap();
226
227 assert_eq!(json["error"]["type"], "validation_error");
228 assert_eq!(json["error"]["message"], "Validation failed");
229 assert_eq!(json["error"]["fields"][0]["field"], "email");
230 }
231
232 #[test]
233 fn validation_error_display() {
234 let error = ValidationError::new(vec![
235 FieldError::new("email", "email", "Invalid email"),
236 FieldError::new("age", "range", "Out of range"),
237 ]);
238
239 assert_eq!(error.to_string(), "Validation failed: 2 field error(s)");
240 }
241}