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