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