1use candid::CandidType;
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, fmt};
4
5#[derive(CandidType, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
7pub struct ErrorTree {
8 pub messages: Vec<String>,
10
11 pub children: HashMap<String, ErrorTree>,
13}
14
15impl ErrorTree {
16 #[must_use]
17 pub fn new() -> Self {
19 Self::default()
20 }
21
22 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) {
39 self.messages.push(message.to_string());
40 }
41
42 pub fn add_result<M: ToString>(&mut self, error: Result<(), M>) {
44 if let Err(e) = error {
45 self.messages.push(e.to_string());
46 }
47 }
48
49 pub fn addf(&mut self, args: fmt::Arguments) {
51 self.messages.push(format!("{args}"));
52 }
53
54 pub fn add_for<K: ToString, M: ToString>(&mut self, key: K, message: M) {
56 self.children
57 .entry(key.to_string())
58 .or_default()
59 .add(message);
60 }
61
62 pub fn merge(&mut self, other: Self) {
64 self.messages.extend(other.messages);
65 for (key, child_errors) in other.children {
66 self.children.entry(key).or_default().merge(child_errors);
67 }
68 }
69
70 #[must_use]
72 pub fn is_empty(&self) -> bool {
73 self.messages.is_empty() && self.children.is_empty()
74 }
75
76 #[must_use]
78 pub fn flatten_ref(&self) -> Vec<(String, String)> {
79 let mut result = Vec::new();
80 self.flatten_helper_ref(String::new(), &mut result);
81 result
82 }
83
84 fn flatten_helper_ref(&self, prefix: String, result: &mut Vec<(String, String)>) {
85 for msg in &self.messages {
87 result.push((prefix.clone(), msg.clone()));
88 }
89 for (key, child) in &self.children {
91 let new_prefix = if prefix.is_empty() {
92 key.clone()
93 } else {
94 format!("{prefix}.{key}")
95 };
96 child.flatten_helper_ref(new_prefix, result);
97 }
98 }
99
100 pub fn result(self) -> Result<(), Self> {
102 if self.is_empty() { Ok(()) } else { Err(self) }
103 }
104}
105
106#[macro_export]
107macro_rules! err {
108 ($errs:expr, $($arg:tt)*) => {{
109 $errs.addf(format_args!($($arg)*));
110 }};
111}
112
113impl fmt::Display for ErrorTree {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 for (key, msg) in self.flatten_ref() {
116 if key.is_empty() {
117 writeln!(f, "{msg}")?;
118 } else {
119 writeln!(f, "{key}: {msg}")?;
120 }
121 }
122
123 Ok(())
124 }
125}
126
127impl From<&str> for ErrorTree {
128 fn from(err: &str) -> Self {
129 let mut tree = Self::new();
130 tree.add(err.to_string());
131
132 tree
133 }
134}
135
136impl From<String> for ErrorTree {
137 fn from(s: String) -> Self {
138 let mut tree = Self::new();
139 tree.add(s);
140
141 tree
142 }
143}
144
145#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_empty_errors() {
155 let errs = ErrorTree::new();
156 assert!(errs.is_empty());
157 assert_eq!(errs.result(), Ok(()));
158 }
159
160 #[test]
161 fn test_add_and_merge() {
162 let mut errs = ErrorTree::new();
163 errs.add("top-level error");
164
165 let mut child_errs = ErrorTree::new();
166 child_errs.add("child error 1");
167 child_errs.add("child error 2");
168 errs.add_for("field", "field error");
169 errs.children
170 .entry("nested".to_string())
171 .or_default()
172 .merge(child_errs);
173
174 assert_eq!(errs.messages.len(), 1);
176 assert!(errs.children.contains_key("field") || errs.children.contains_key("nested"));
177
178 let flat = errs.flatten_ref();
180 assert_eq!(flat.len(), 4);
181 }
182}