1use crate::path::FieldPath;
21use std::collections::BTreeMap;
22use std::fmt;
23
24#[cfg(feature = "serde")]
25use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Clone)]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
39pub struct ValidationError {
40 pub path: FieldPath,
42
43 pub code: String,
45
46 pub message: String,
48
49 #[cfg_attr(
51 feature = "serde",
52 serde(default, skip_serializing_if = "BTreeMap::is_empty")
53 )]
54 pub params: BTreeMap<String, ErrorParam>,
55}
56
57#[derive(Debug, Clone)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60#[cfg_attr(feature = "serde", serde(untagged))]
61pub enum ErrorParam {
62 String(String),
64 Int(i64),
66 Float(f64),
68 Bool(bool),
70 List(Vec<String>),
72}
73
74impl From<String> for ErrorParam {
75 fn from(s: String) -> Self {
76 Self::String(s)
77 }
78}
79
80impl From<&str> for ErrorParam {
81 fn from(s: &str) -> Self {
82 Self::String(s.to_string())
83 }
84}
85
86impl From<i64> for ErrorParam {
87 fn from(n: i64) -> Self {
88 Self::Int(n)
89 }
90}
91
92impl From<i32> for ErrorParam {
93 fn from(n: i32) -> Self {
94 Self::Int(n as i64)
95 }
96}
97
98impl From<usize> for ErrorParam {
99 fn from(n: usize) -> Self {
100 Self::Int(n as i64)
101 }
102}
103
104impl From<f64> for ErrorParam {
105 fn from(n: f64) -> Self {
106 Self::Float(n)
107 }
108}
109
110impl From<bool> for ErrorParam {
111 fn from(b: bool) -> Self {
112 Self::Bool(b)
113 }
114}
115
116impl From<Vec<String>> for ErrorParam {
117 fn from(v: Vec<String>) -> Self {
118 Self::List(v)
119 }
120}
121
122impl ValidationError {
123 pub fn new(
131 field: impl Into<String>,
132 code: impl Into<String>,
133 message: impl Into<String>,
134 ) -> Self {
135 Self {
136 path: FieldPath::from_field(field),
137 code: code.into(),
138 message: message.into(),
139 params: BTreeMap::new(),
140 }
141 }
142
143 pub fn with_path(
145 path: FieldPath,
146 code: impl Into<String>,
147 message: impl Into<String>,
148 ) -> Self {
149 Self {
150 path,
151 code: code.into(),
152 message: message.into(),
153 params: BTreeMap::new(),
154 }
155 }
156
157 pub fn root(code: impl Into<String>, message: impl Into<String>) -> Self {
159 Self {
160 path: FieldPath::new(),
161 code: code.into(),
162 message: message.into(),
163 params: BTreeMap::new(),
164 }
165 }
166
167 pub fn with_param(mut self, key: impl Into<String>, value: impl Into<ErrorParam>) -> Self {
169 self.params.insert(key.into(), value.into());
170 self
171 }
172
173 pub fn field_name(&self) -> Option<&str> {
175 self.path.last_field_name()
176 }
177}
178
179impl fmt::Display for ValidationError {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 if self.path.is_empty() {
182 write!(f, "{}", self.message)
183 } else {
184 write!(f, "{}: {}", self.path, self.message)
185 }
186 }
187}
188
189impl std::error::Error for ValidationError {}
190
191#[derive(Debug, Clone)]
193#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
194#[cfg_attr(feature = "serde", serde(untagged))]
195pub enum FieldErrors {
196 Simple(Vec<ValidationError>),
198
199 Nested(Box<ValidationErrors>),
201
202 List(BTreeMap<usize, Box<ValidationErrors>>),
204
205 Map(BTreeMap<String, Box<ValidationErrors>>),
207}
208
209impl FieldErrors {
210 pub fn simple(errors: Vec<ValidationError>) -> Self {
212 Self::Simple(errors)
213 }
214
215 pub fn nested(errors: ValidationErrors) -> Self {
217 Self::Nested(Box::new(errors))
218 }
219
220 pub fn list(errors: BTreeMap<usize, Box<ValidationErrors>>) -> Self {
222 Self::List(errors)
223 }
224
225 pub fn map(errors: BTreeMap<String, Box<ValidationErrors>>) -> Self {
227 Self::Map(errors)
228 }
229
230 pub fn is_empty(&self) -> bool {
232 match self {
233 Self::Simple(v) => v.is_empty(),
234 Self::Nested(n) => n.is_empty(),
235 Self::List(m) => m.is_empty(),
236 Self::Map(m) => m.is_empty(),
237 }
238 }
239
240 pub fn count(&self) -> usize {
242 match self {
243 Self::Simple(v) => v.len(),
244 Self::Nested(n) => n.count(),
245 Self::List(m) => m.values().map(|v| v.count()).sum(),
246 Self::Map(m) => m.values().map(|v| v.count()).sum(),
247 }
248 }
249}
250
251#[derive(Debug, Clone, Default)]
265#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
266pub struct ValidationErrors {
267 #[cfg_attr(
269 feature = "serde",
270 serde(default, skip_serializing_if = "Vec::is_empty")
271 )]
272 pub errors: Vec<ValidationError>,
273
274 #[cfg_attr(
276 feature = "serde",
277 serde(default, skip_serializing_if = "BTreeMap::is_empty")
278 )]
279 pub fields: BTreeMap<String, FieldErrors>,
280}
281
282impl ValidationErrors {
283 pub fn new() -> Self {
285 Self::default()
286 }
287
288 pub fn from_error(error: ValidationError) -> Self {
290 let mut errors = Self::new();
291 if let Some(field) = error.field_name().map(|s| s.to_string()) {
292 errors.add_field_error(field, error);
293 } else {
294 errors.add_root_error(error);
295 }
296 errors
297 }
298
299 pub fn is_empty(&self) -> bool {
301 self.errors.is_empty() && self.fields.is_empty()
302 }
303
304 pub fn count(&self) -> usize {
306 self.errors.len() + self.fields.values().map(|f| f.count()).sum::<usize>()
307 }
308
309 pub fn add_root_error(&mut self, error: ValidationError) {
311 self.errors.push(error);
312 }
313
314 pub fn add_field_error(&mut self, field: impl Into<String>, error: ValidationError) {
316 let field = field.into();
317 match self.fields.entry(field) {
318 std::collections::btree_map::Entry::Occupied(mut e) => {
319 if let FieldErrors::Simple(vec) = e.get_mut() {
320 vec.push(error);
321 }
322 }
323 std::collections::btree_map::Entry::Vacant(e) => {
324 e.insert(FieldErrors::Simple(vec![error]));
325 }
326 }
327 }
328
329 pub fn add_nested_errors(&mut self, field: impl Into<String>, errors: ValidationErrors) {
331 if !errors.is_empty() {
332 self.fields
333 .insert(field.into(), FieldErrors::Nested(Box::new(errors)));
334 }
335 }
336
337 pub fn add_list_errors(
339 &mut self,
340 field: impl Into<String>,
341 errors: BTreeMap<usize, Box<ValidationErrors>>,
342 ) {
343 if !errors.is_empty() {
344 self.fields.insert(field.into(), FieldErrors::List(errors));
345 }
346 }
347
348 pub fn add_map_errors(
350 &mut self,
351 field: impl Into<String>,
352 errors: BTreeMap<String, Box<ValidationErrors>>,
353 ) {
354 if !errors.is_empty() {
355 self.fields.insert(field.into(), FieldErrors::Map(errors));
356 }
357 }
358
359 pub fn merge(&mut self, other: ValidationErrors) {
361 self.errors.extend(other.errors);
362 for (field, errors) in other.fields {
363 self.fields.insert(field, errors);
364 }
365 }
366
367 pub fn merge_result<T>(&mut self, result: Result<T, ValidationErrors>) -> Option<T> {
369 match result {
370 Ok(v) => Some(v),
371 Err(e) => {
372 self.merge(e);
373 None
374 }
375 }
376 }
377
378 pub fn to_flat_map(&self) -> BTreeMap<String, Vec<String>> {
382 let mut result = BTreeMap::new();
383 self.flatten_into("", &mut result);
384 result
385 }
386
387 fn flatten_into(&self, prefix: &str, result: &mut BTreeMap<String, Vec<String>>) {
388 for error in &self.errors {
390 let key = if prefix.is_empty() {
391 "_root".to_string()
392 } else {
393 prefix.to_string()
394 };
395 result.entry(key).or_default().push(error.message.clone());
396 }
397
398 for (field, errors) in &self.fields {
400 let path = if prefix.is_empty() {
401 field.clone()
402 } else {
403 format!("{}.{}", prefix, field)
404 };
405
406 match errors {
407 FieldErrors::Simple(vec) => {
408 for error in vec {
409 result.entry(path.clone()).or_default().push(error.message.clone());
410 }
411 }
412 FieldErrors::Nested(nested) => {
413 nested.flatten_into(&path, result);
414 }
415 FieldErrors::List(list) => {
416 for (idx, nested) in list {
417 let item_path = format!("{}[{}]", path, idx);
418 nested.flatten_into(&item_path, result);
419 }
420 }
421 FieldErrors::Map(map) => {
422 for (key, nested) in map {
423 let item_path = format!("{}[{}]", path, key);
424 nested.flatten_into(&item_path, result);
425 }
426 }
427 }
428 }
429 }
430
431 pub fn messages(&self) -> Vec<String> {
433 let flat = self.to_flat_map();
434 flat.into_values().flatten().collect()
435 }
436
437 pub fn field(&self, name: &str) -> Option<&FieldErrors> {
439 self.fields.get(name)
440 }
441
442 pub fn has_field_error(&self, name: &str) -> bool {
444 self.fields.contains_key(name)
445 }
446
447 pub fn field_count(&self) -> usize {
449 self.fields.len()
450 }
451
452 pub fn field_errors(&self) -> impl Iterator<Item = (&String, &FieldErrors)> {
454 self.fields.iter()
455 }
456
457 #[cfg(feature = "serde")]
459 pub fn to_json(&self) -> serde_json::Value {
460 serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
461 }
462
463 #[cfg(feature = "serde")]
465 pub fn to_json_string(&self) -> String {
466 serde_json::to_string(self).unwrap_or_default()
467 }
468
469 #[cfg(feature = "serde")]
471 pub fn to_json_pretty(&self) -> String {
472 serde_json::to_string_pretty(self).unwrap_or_default()
473 }
474}
475
476impl std::error::Error for ValidationErrors {}
477
478impl fmt::Display for ValidationErrors {
479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480 let flat = self.to_flat_map();
481 let mut first = true;
482 for (field, messages) in flat {
483 for msg in messages {
484 if !first {
485 writeln!(f)?;
486 }
487 first = false;
488 if field == "_root" {
489 write!(f, "{}", msg)?;
490 } else {
491 write!(f, "{}: {}", field, msg)?;
492 }
493 }
494 }
495 Ok(())
496 }
497}
498
499impl FromIterator<ValidationError> for ValidationErrors {
500 fn from_iter<I: IntoIterator<Item = ValidationError>>(iter: I) -> Self {
501 let mut errors = ValidationErrors::new();
502 for error in iter {
503 if let Some(field) = error.field_name() {
504 errors.add_field_error(field.to_string(), error);
505 } else {
506 errors.add_root_error(error);
507 }
508 }
509 errors
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn test_single_error() {
519 let mut errors = ValidationErrors::new();
520 errors.add_field_error(
521 "email",
522 ValidationError::new("email", "email.invalid", "Invalid email"),
523 );
524
525 assert!(!errors.is_empty());
526 assert_eq!(errors.count(), 1);
527 assert!(errors.has_field_error("email"));
528 }
529
530 #[test]
531 fn test_multiple_errors() {
532 let mut errors = ValidationErrors::new();
533 errors.add_field_error(
534 "email",
535 ValidationError::new("email", "email.invalid", "Invalid email"),
536 );
537 errors.add_field_error(
538 "name",
539 ValidationError::new("name", "required", "Name is required"),
540 );
541
542 assert_eq!(errors.count(), 2);
543 }
544
545 #[test]
546 fn test_flat_map() {
547 let mut errors = ValidationErrors::new();
548 errors.add_field_error(
549 "email",
550 ValidationError::new("email", "email.invalid", "Invalid email"),
551 );
552
553 let flat = errors.to_flat_map();
554 assert!(flat.contains_key("email"));
555 assert_eq!(flat["email"][0], "Invalid email");
556 }
557
558 #[test]
559 fn test_nested_errors() {
560 let mut inner = ValidationErrors::new();
561 inner.add_field_error(
562 "city",
563 ValidationError::new("city", "required", "City is required"),
564 );
565
566 let mut outer = ValidationErrors::new();
567 outer.add_nested_errors("address", inner);
568
569 assert_eq!(outer.count(), 1);
570 let flat = outer.to_flat_map();
571 assert!(flat.contains_key("address.city"));
572 }
573}