1/// A simple macro for returning an error. Resembles anyhow::bail.
2#[macro_export]
3#[doc(hidden)]
4macro_rules! bail {
5 ($($args: tt)+) => { return Err(format!($($args)+).into()) };
6}
7
8/// A simple macro for checking a condition. Resembles anyhow::ensure.
9#[macro_export]
10#[doc(hidden)]
11macro_rules! ensure {
12 ($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } };
13}
14
15/// Show warning.
16#[macro_export]
17#[doc(hidden)]
18macro_rules! warn {
19 ($($args: tt)+) => {
20 println!("{}", $crate::format_warn!($($args)+))
21 };
22}
23
24/// Format warning into string.
25#[macro_export]
26#[doc(hidden)]
27macro_rules! format_warn {
28 ($($args: tt)+) => {
29 format!("cargo:warning={}", format_args!($($args)+))
30 };
31}
32
33/// A simple error implementation which allows chaining of errors, inspired somewhat by anyhow.
34#[derive(Debug)]
35pub struct Error {
36 value: String,
37 source: Option<Box<dyn std::error::Error>>,
38}
39
40/// Error report inspired by
41/// <https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html#2-error-reporter>
42pub struct ErrorReport<'a>(&'a Error);
43
44impl Error {
45 pub fn report(&self) -> ErrorReport<'_> {
46 ErrorReport(self)
47 }
48}
49
50impl std::fmt::Display for Error {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 write!(f, "{}", self.value)
53 }
54}
55
56impl std::error::Error for Error {
57 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
58 self.source.as_deref()
59 }
60}
61
62impl std::fmt::Display for ErrorReport<'_> {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 use std::error::Error;
65 self.0.fmt(f)?;
66 let mut source: Option<&dyn Error> = self.0.source();
67 if source.is_some() {
68 writeln!(f, "\ncaused by:")?;
69 let mut index: i32 = 0;
70 while let Some(some_source: &dyn Error) = source {
71 writeln!(f, " - {}: {}", index, some_source)?;
72 source = some_source.source();
73 index += 1;
74 }
75 }
76 Ok(())
77 }
78}
79
80impl From<String> for Error {
81 fn from(value: String) -> Self {
82 Self {
83 value,
84 source: None,
85 }
86 }
87}
88
89impl From<&'_ str> for Error {
90 fn from(value: &str) -> Self {
91 value.to_string().into()
92 }
93}
94
95impl From<std::convert::Infallible> for Error {
96 fn from(value: std::convert::Infallible) -> Self {
97 match value {}
98 }
99}
100
101pub type Result<T, E = Error> = std::result::Result<T, E>;
102
103pub trait Context<T> {
104 fn context(self, message: impl Into<String>) -> Result<T>;
105 fn with_context(self, message: impl FnOnce() -> String) -> Result<T>;
106}
107
108impl<T, E> Context<T> for Result<T, E>
109where
110 E: std::error::Error + 'static,
111{
112 fn context(self, message: impl Into<String>) -> Result<T> {
113 self.map_err(|error: E| Error {
114 value: message.into(),
115 source: Some(Box::new(error)),
116 })
117 }
118
119 fn with_context(self, message: impl FnOnce() -> String) -> Result<T> {
120 self.map_err(|error: E| Error {
121 value: message(),
122 source: Some(Box::new(error)),
123 })
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn error_report() {
133 let error: Result<()> = Err(Error::from("there was an internal error"))
134 .with_context(|| format!("failed to do {}", "something difficult"))
135 .context("things went wrong");
136
137 assert_eq!(
138 error
139 .unwrap_err()
140 .report()
141 .to_string()
142 .split('\n')
143 .collect::<Vec<&str>>(),
144 vec![
145 "things went wrong",
146 "caused by:",
147 " - 0: failed to do something difficult",
148 " - 1: there was an internal error",
149 ""
150 ]
151 );
152 }
153}
154