snarkvm_utilities/
errors.rs1use std::{borrow::Borrow, fmt::Display};
17
18#[inline]
20pub fn io_error<S: ToString>(err: S) -> std::io::Error {
21 std::io::Error::other(err.to_string())
22}
23
24#[inline]
28pub fn into_io_error<E: Into<anyhow::Error>>(err: E) -> std::io::Error {
29 let err: anyhow::Error = err.into();
30 std::io::Error::other(flatten_anyhow_error(&err))
31}
32
33#[inline]
35fn flatten_anyhow_error<E: Borrow<anyhow::Error>>(error: E) -> String {
36 let error = error.borrow();
37 let mut output = error.to_string();
38 for next in error.chain().skip(1) {
39 output = format!("{output} — {next}");
40 }
41 output
42}
43
44#[inline]
50#[track_caller]
51pub fn log_error<E: Borrow<anyhow::Error>>(error: E) {
52 tracing::error!("{}", flatten_anyhow_error(error));
53}
54
55#[inline]
61#[track_caller]
62pub fn log_warning<E: Borrow<anyhow::Error>>(error: E) {
63 tracing::warn!("{}", flatten_anyhow_error(error));
64}
65
66#[track_caller]
70#[inline]
71pub fn display_error(error: &anyhow::Error) {
72 eprintln!("⚠️ {error}");
73 error.chain().skip(1).for_each(|cause| eprintln!(" ↳ {cause}"));
74}
75
76#[macro_export]
83macro_rules! ensure_equals {
84 ($actual:expr, $expected:expr, $message:expr) => {
85 if $actual != $expected {
86 anyhow::bail!("{}: Was {} but expected {}.", $message, $actual, $expected);
87 }
88 };
89}
90
91pub trait LoggableError {
106 fn log_error<S: Send + Sync + Display + 'static>(self, context: S);
108 fn log_warning<S: Send + Sync + Display + 'static>(self, context: S);
110 fn log_debug<S: Send + Sync + Display + 'static>(self, context: S);
112}
113
114impl<E: Into<anyhow::Error>> LoggableError for E {
115 #[track_caller]
117 #[inline]
118 fn log_error<S: Send + Sync + Display + 'static>(self, context: S) {
119 let err: anyhow::Error = self.into();
120 log_error(err.context(context));
121 }
122
123 #[track_caller]
125 #[inline]
126 fn log_warning<S: Send + Sync + Display + 'static>(self, context: S) {
127 let err: anyhow::Error = self.into();
128 log_warning(err.context(context));
129 }
130
131 #[track_caller]
133 #[inline]
134 fn log_debug<S: Send + Sync + Display + 'static>(self, context: S) {
135 let err: anyhow::Error = self.into();
136 log_warning(err.context(context));
137 }
138}
139
140pub trait PrettyUnwrap {
142 type Inner;
143
144 fn pretty_unwrap(self) -> Self::Inner;
146
147 fn pretty_expect<S: ToString>(self, context: S) -> Self::Inner;
149}
150
151#[track_caller]
153#[inline]
154fn pretty_panic(error: &anyhow::Error) -> ! {
155 let mut string = format!("⚠️ {error}");
156 error.chain().skip(1).for_each(|cause| string.push_str(&format!("\n ↳ {cause}")));
157 let caller = std::panic::Location::caller();
158
159 tracing::error!("[{}:{}] {string}", caller.file(), caller.line());
160 panic!("{string}");
161}
162
163impl<T> PrettyUnwrap for anyhow::Result<T> {
165 type Inner = T;
166
167 #[track_caller]
168 fn pretty_unwrap(self) -> Self::Inner {
169 match self {
170 Ok(result) => result,
171 Err(error) => {
172 pretty_panic(&error);
173 }
174 }
175 }
176
177 #[track_caller]
178 fn pretty_expect<S: ToString>(self, context: S) -> Self::Inner {
179 match self {
180 Ok(result) => result,
181 Err(error) => {
182 pretty_panic(&error.context(context.to_string()));
183 }
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::{PrettyUnwrap, flatten_anyhow_error, pretty_panic};
191
192 use anyhow::{Context, Result, anyhow, bail};
193
194 const ERRORS: [&str; 3] = ["Third error", "Second error", "First error"];
195
196 #[test]
197 fn flatten_error() {
198 let expected = format!("{} — {} — {}", ERRORS[0], ERRORS[1], ERRORS[2]);
199
200 let my_error = anyhow!(ERRORS[2]).context(ERRORS[1]).context(ERRORS[0]);
201 let result = flatten_anyhow_error(&my_error);
202
203 assert_eq!(result, expected);
204 }
205
206 #[test]
207 fn chained_error_panic_format() {
208 let expected = format!("⚠️ {}\n ↳ {}\n ↳ {}", ERRORS[0], ERRORS[1], ERRORS[2]);
209
210 let result = std::panic::catch_unwind(|| {
211 let my_error = anyhow!(ERRORS[2]).context(ERRORS[1]).context(ERRORS[0]);
212 pretty_panic(&my_error);
213 })
214 .unwrap_err();
215
216 assert_eq!(*result.downcast::<String>().expect("Error was not a string"), expected);
217 }
218
219 #[test]
220 fn chained_pretty_unwrap_format() {
221 let expected = format!("⚠️ {}\n ↳ {}\n ↳ {}", ERRORS[0], ERRORS[1], ERRORS[2]);
222
223 let result = std::panic::catch_unwind(|| {
225 fn level2() -> Result<()> {
226 bail!(ERRORS[2]);
227 }
228
229 fn level1() -> Result<()> {
230 level2().with_context(|| ERRORS[1])?;
231 Ok(())
232 }
233
234 fn level0() -> Result<()> {
235 level1().with_context(|| ERRORS[0])?;
236 Ok(())
237 }
238
239 level0().pretty_unwrap();
240 })
241 .unwrap_err();
242
243 assert_eq!(*result.downcast::<String>().expect("Error was not a string"), expected);
244 }
245
246 #[test]
248 fn test_nested_with_try_vm_runtime() {
249 use crate::try_vm_runtime;
250
251 let result = std::panic::catch_unwind(|| {
252 let vm_result = try_vm_runtime!(|| {
254 panic!("VM operation failed!");
255 });
256
257 assert!(vm_result.is_err(), "try_vm_runtime should catch VM panic");
258
259 "handled_vm_error"
261 });
262
263 assert!(result.is_ok(), "Should handle VM error gracefully");
264 assert_eq!(result.unwrap(), "handled_vm_error");
265 }
266}