1use std::collections::HashMap;
2
3use crate::error::VldError;
4
5#[derive(Debug, Clone)]
10#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
11#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
12pub struct FlatError {
13 pub form_errors: Vec<String>,
14 pub field_errors: HashMap<String, Vec<String>>,
15}
16
17pub fn flatten_error(error: &VldError) -> FlatError {
29 let mut form_errors = Vec::new();
30 let mut field_errors: HashMap<String, Vec<String>> = HashMap::new();
31
32 for issue in &error.issues {
33 if issue.path.is_empty() {
34 form_errors.push(issue.message.clone());
35 } else {
36 let key = issue.path[0].to_string();
37 let key = key.strip_prefix('.').unwrap_or(&key).to_string();
39 field_errors
40 .entry(key)
41 .or_default()
42 .push(issue.message.clone());
43 }
44 }
45
46 FlatError {
47 form_errors,
48 field_errors,
49 }
50}
51
52#[derive(Debug, Clone, Default)]
54#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
55#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
56pub struct ErrorTree {
57 pub errors: Vec<String>,
58 pub properties: HashMap<String, ErrorTree>,
59 pub items: Vec<Option<ErrorTree>>,
60}
61
62pub fn treeify_error(error: &VldError) -> ErrorTree {
64 let mut root = ErrorTree::default();
65
66 for issue in &error.issues {
67 if issue.path.is_empty() {
68 root.errors.push(issue.message.clone());
69 continue;
70 }
71
72 let mut current = &mut root;
73
74 for (i, segment) in issue.path.iter().enumerate() {
75 let is_last = i == issue.path.len() - 1;
76
77 match segment {
78 crate::error::PathSegment::Field(name) => {
79 if !current.properties.contains_key(name) {
80 current
81 .properties
82 .insert(name.clone(), ErrorTree::default());
83 }
84 if is_last {
85 current
86 .properties
87 .get_mut(name)
88 .unwrap()
89 .errors
90 .push(issue.message.clone());
91 } else {
92 current = current.properties.get_mut(name).unwrap();
93 }
94 }
95 crate::error::PathSegment::Index(idx) => {
96 while current.items.len() <= *idx {
97 current.items.push(None);
98 }
99 if current.items[*idx].is_none() {
100 current.items[*idx] = Some(ErrorTree::default());
101 }
102 if is_last {
103 current.items[*idx]
104 .as_mut()
105 .unwrap()
106 .errors
107 .push(issue.message.clone());
108 } else {
109 current = current.items[*idx].as_mut().unwrap();
110 }
111 }
112 }
113 }
114 }
115
116 root
117}
118
119pub fn prettify_error(error: &VldError) -> String {
129 let mut lines = Vec::new();
130
131 for issue in &error.issues {
132 lines.push(format!("✖ {}", issue.message));
133 if !issue.path.is_empty() || issue.received.is_some() {
134 let mut parts = Vec::new();
135 if !issue.path.is_empty() {
136 let path_str: String = issue.path.iter().map(|p| p.to_string()).collect();
137 parts.push(format!("at {}", path_str));
138 }
139 if let Some(val) = &issue.received {
140 parts.push(format!(
141 "received {}",
142 crate::error::format_value_short(val)
143 ));
144 }
145 lines.push(format!(" → {}", parts.join(", ")));
146 }
147 }
148
149 lines.join("\n")
150}