errors.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaVM library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 use colored::Colorize; 17 use std::borrow::Borrow; 18 19 /// Generates an `io::Error` from the given string. 20 #[inline] 21 pub fn io_error<S: ToString>(err: S) -> std::io::Error { 22 std::io::Error::other(err.to_string()) 23 } 24 25 /// Generates an `io::Error` from the given `anyhow::Error`. 26 /// 27 /// This will flatten the existing error chain so that it fits in a single-line string. 28 #[inline] 29 pub fn into_io_error<E: Into<anyhow::Error>>(err: E) -> std::io::Error { 30 let err: anyhow::Error = err.into(); 31 std::io::Error::other(flatten_error(&err)) 32 } 33 34 /// Converts an `anyhow::Error` into a single-line string. 35 /// 36 /// This follows the existing convention in the codebase that joins errors using em dashes. 37 /// For example, an error "Invalid transaction" with a cause "Proof failed" would be logged 38 /// as "Invalid transaction — Proof failed". 39 #[inline] 40 pub fn flatten_error<E: Borrow<anyhow::Error>>(error: E) -> String { 41 let error = error.borrow(); 42 let chain = error.chain().skip(1).map(|next| next.to_string()).collect::<Vec<String>>().join(" — "); 43 format!("{error}{}", format!(" — {chain}").dimmed()) 44 } 45 46 /// Displays an `anyhow::Error`'s main error and its error chain to stderr. 47 /// 48 /// This can be used to show a "pretty" error to the end user. 49 #[track_caller] 50 #[inline] 51 pub fn display_error<E: Borrow<anyhow::Error>>(error: E) { 52 let error = error.borrow(); 53 eprintln!("⚠️ {error}"); 54 error.chain().skip(1).for_each(|cause| eprintln!(" ↳ {cause}")); 55 } 56 57 /// Ensures that two values are equal, otherwise bails with a formatted error message. 58 /// 59 /// # Arguments 60 /// * `actual` - The actual value 61 /// * `expected` - The expected value 62 /// * `message` - A description of what was being checked 63 #[macro_export] 64 macro_rules! ensure_equals { 65 ($actual:expr, $expected:expr, $message:expr) => { 66 if $actual != $expected { 67 anyhow::bail!("{}: Was {} but expected {}.", $message, $actual, $expected); 68 } 69 }; 70 } 71 72 /// A trait to provide a nicer way to unwarp `anyhow::Result`. 73 pub trait PrettyUnwrap { 74 type Inner; 75 76 /// Behaves like [`std::result::Result::unwrap`] but will print the entire anyhow chain to stderr. 77 fn pretty_unwrap(self) -> Self::Inner; 78 79 /// Behaves like [`std::result::Result::expect`] but will print the entire anyhow chain to stderr. 80 fn pretty_expect<S: ToString>(self, context: S) -> Self::Inner; 81 } 82 83 /// Helper for `PrettyUnwrap`, which creates a panic with the `anyhow::Error` nicely formatted and also logs the panic. 84 #[track_caller] 85 #[inline] 86 fn pretty_panic(error: &anyhow::Error) -> ! { 87 let mut string = format!("⚠️ {error}"); 88 error.chain().skip(1).for_each(|cause| string.push_str(&format!("\n ↳ {cause}"))); 89 let caller = std::panic::Location::caller(); 90 91 tracing::error!("[{}:{}] {string}", caller.file(), caller.line()); 92 panic!("{string}"); 93 } 94 95 /// Implement the trait for `anyhow::Result`. 96 impl<T> PrettyUnwrap for anyhow::Result<T> { 97 type Inner = T; 98 99 #[track_caller] 100 fn pretty_unwrap(self) -> Self::Inner { 101 match self { 102 Ok(result) => result, 103 Err(error) => { 104 pretty_panic(&error); 105 } 106 } 107 } 108 109 #[track_caller] 110 fn pretty_expect<S: ToString>(self, context: S) -> Self::Inner { 111 match self { 112 Ok(result) => result, 113 Err(error) => { 114 pretty_panic(&error.context(context.to_string())); 115 } 116 } 117 } 118 } 119 120 #[cfg(test)] 121 mod tests { 122 use super::{PrettyUnwrap, flatten_error, pretty_panic}; 123 124 use anyhow::{Context, Result, anyhow, bail}; 125 use colored::Colorize; 126 127 const ERRORS: [&str; 3] = ["Third error", "Second error", "First error"]; 128 129 #[test] 130 fn test_flatten_error() { 131 // First error should be printed regularly, the other two dimmed. 132 let expected = format!("{}{}", ERRORS[0], format!(" — {} — {}", ERRORS[1], ERRORS[2]).dimmed()); 133 134 let my_error = anyhow!(ERRORS[2]).context(ERRORS[1]).context(ERRORS[0]); 135 let result = flatten_error(&my_error); 136 137 assert_eq!(result, expected); 138 } 139 140 #[test] 141 fn chained_error_panic_format() { 142 let expected = format!("⚠️ {}\n ↳ {}\n ↳ {}", ERRORS[0], ERRORS[1], ERRORS[2]); 143 144 let result = std::panic::catch_unwind(|| { 145 let my_error = anyhow!(ERRORS[2]).context(ERRORS[1]).context(ERRORS[0]); 146 pretty_panic(&my_error); 147 }) 148 .unwrap_err(); 149 150 assert_eq!(*result.downcast::<String>().expect("Error was not a string"), expected); 151 } 152 153 #[test] 154 fn chained_pretty_unwrap_format() { 155 let expected = format!("⚠️ {}\n ↳ {}\n ↳ {}", ERRORS[0], ERRORS[1], ERRORS[2]); 156 157 // Also test `pretty_unwrap` and chaining errors across functions. 158 let result = std::panic::catch_unwind(|| { 159 fn level2() -> Result<()> { 160 bail!(ERRORS[2]); 161 } 162 163 fn level1() -> Result<()> { 164 level2().with_context(|| ERRORS[1])?; 165 Ok(()) 166 } 167 168 fn level0() -> Result<()> { 169 level1().with_context(|| ERRORS[0])?; 170 Ok(()) 171 } 172 173 level0().pretty_unwrap(); 174 }) 175 .unwrap_err(); 176 177 assert_eq!(*result.downcast::<String>().expect("Error was not a string"), expected); 178 } 179 180 /// Ensure catch_unwind does not break `try_vm_runtime`. 181 #[test] 182 fn test_nested_with_try_vm_runtime() { 183 use crate::try_vm_runtime; 184 185 let result = std::panic::catch_unwind(|| { 186 // try_vm_runtime uses catch_unwind internally 187 let vm_result = try_vm_runtime!(|| { 188 panic!("VM operation failed!"); 189 }); 190 191 assert!(vm_result.is_err(), "try_vm_runtime should catch VM panic"); 192 193 // We can handle the VM error gracefully 194 "handled_vm_error" 195 }); 196 197 assert!(result.is_ok(), "Should handle VM error gracefully"); 198 assert_eq!(result.unwrap(), "handled_vm_error"); 199 } 200 }