1use candid::CandidType;
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, fmt};
4
5#[derive(CandidType, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
11pub struct ErrorTree {
12 messages: Vec<String>,
14
15 children: HashMap<String, Self>,
17}
18
19impl ErrorTree {
20 #[must_use]
22 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub fn collect<I>(iter: I) -> Result<(), Self>
28 where
29 I: IntoIterator<Item = Result<(), Self>>,
30 {
31 let mut errs = Self::new();
32 for res in iter {
33 if let Err(e) = res {
34 errs.merge(e);
35 }
36 }
37
38 errs.result()
39 }
40
41 pub fn add<M: ToString>(&mut self, message: M) {
43 self.messages.push(message.to_string());
44 }
45
46 pub fn add_result<M: ToString>(&mut self, error: Result<(), M>) {
48 if let Err(e) = error {
49 self.messages.push(e.to_string());
50 }
51 }
52
53 pub fn addf(&mut self, args: fmt::Arguments) {
55 self.messages.push(format!("{args}"));
56 }
57
58 pub fn add_for<K: ToString, M: ToString>(&mut self, key: K, message: M) {
60 self.children
61 .entry(key.to_string())
62 .or_default()
63 .add(message);
64 }
65
66 pub fn merge(&mut self, other: Self) {
68 self.messages.extend(other.messages);
69 for (key, child_errors) in other.children {
70 self.children.entry(key).or_default().merge(child_errors);
71 }
72 }
73
74 pub fn merge_for<K: ToString>(&mut self, key: K, other: Self) {
76 self.children
77 .entry(key.to_string())
78 .or_default()
79 .merge(other);
80 }
81
82 #[must_use]
84 pub fn is_empty(&self) -> bool {
85 self.messages.is_empty() && self.children.is_empty()
86 }
87
88 #[must_use]
90 pub fn messages(&self) -> &[String] {
91 &self.messages
92 }
93
94 #[must_use]
96 pub const fn children(&self) -> &HashMap<String, Self> {
97 &self.children
98 }
99
100 #[must_use]
102 pub fn flatten_ref(&self) -> Vec<(String, String)> {
103 let mut result = Vec::new();
104 self.flatten_helper_ref(String::new(), &mut result);
105 result
106 }
107
108 fn flatten_helper_ref(&self, prefix: String, result: &mut Vec<(String, String)>) {
109 for msg in &self.messages {
111 result.push((prefix.clone(), msg.clone()));
112 }
113 for (key, child) in &self.children {
115 let new_prefix = if prefix.is_empty() {
116 key.clone()
117 } else {
118 format!("{prefix}.{key}")
119 };
120 child.flatten_helper_ref(new_prefix, result);
121 }
122 }
123
124 pub fn result(self) -> Result<(), Self> {
126 if self.is_empty() { Ok(()) } else { Err(self) }
127 }
128}
129
130#[macro_export]
131macro_rules! err {
132 ($errs:expr, $($arg:tt)*) => {{
133 $errs.addf(format_args!($($arg)*));
134 }};
135}
136
137impl fmt::Display for ErrorTree {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 for (key, msg) in self.flatten_ref() {
140 if key.is_empty() {
141 writeln!(f, "{msg}")?;
142 } else {
143 writeln!(f, "{key}: {msg}")?;
144 }
145 }
146
147 Ok(())
148 }
149}
150
151impl From<&str> for ErrorTree {
152 fn from(err: &str) -> Self {
153 let mut tree = Self::new();
154 tree.add(err.to_string());
155
156 tree
157 }
158}
159
160impl From<String> for ErrorTree {
161 fn from(s: String) -> Self {
162 let mut tree = Self::new();
163 tree.add(s);
164
165 tree
166 }
167}
168
169#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_empty_errors() {
179 let errs = ErrorTree::new();
180 assert!(errs.is_empty());
181 assert_eq!(errs.result(), Ok(()));
182 }
183
184 #[test]
185 fn test_add_and_merge() {
186 let mut errs = ErrorTree::new();
187 errs.add("top-level error");
188
189 let mut child_errs = ErrorTree::new();
190 child_errs.add("child error 1");
191 child_errs.add("child error 2");
192 errs.add_for("field", "field error");
193 errs.merge_for("nested", child_errs);
194
195 assert_eq!(errs.messages().len(), 1);
197 assert!(errs.children().contains_key("field") || errs.children().contains_key("nested"));
198
199 let flat = errs.flatten_ref();
201 assert_eq!(flat.len(), 4);
202 }
203}