Skip to main content

radiate_error/
lib.rs

1use thiserror::Error;
2#[cfg(feature = "python")]
3pub mod python;
4
5pub type Result<T> = std::result::Result<T, RadiateError>;
6
7#[derive(Copy, Clone, Debug, PartialEq, Eq)]
8pub enum Code {
9    Builder,
10    Engine,
11    Step,
12    Genome,
13    Fitness,
14
15    Metric,
16    Expr,
17    AnyValue,
18
19    Python,
20
21    Multiple,
22    Context,
23    IO,
24
25    Other,
26}
27
28#[derive(Error, Debug)]
29pub enum RadiateError {
30    #[error("Builder error: {0}")]
31    Builder(String),
32
33    #[error("Engine error: {0}")]
34    Engine(String),
35
36    #[error("Step error: {0}")]
37    Step(String),
38
39    #[error("Genome error: {0}")]
40    Genome(String),
41
42    #[error("Invalid fitness: {0}")]
43    Fitness(String),
44
45    #[error("Metric error: {0}")]
46    Metric(String),
47
48    #[error("Expr error: {0}")]
49    Expr(String),
50
51    #[cfg(feature = "python")]
52    #[error("Python error: {0}")]
53    Python(#[from] pyo3::PyErr),
54
55    #[error("Multiple errors:\n{0}")]
56    Multiple(String),
57
58    #[error("AnyValue error: {0}")]
59    AnyValue(String),
60
61    #[error("Other error: {0}")]
62    Other(String),
63
64    #[error("{context}\nCaused by: {source}")]
65    Context {
66        context: String,
67        #[source]
68        source: Box<RadiateError>,
69    },
70
71    #[error("I/O error: {0}")]
72    IO(#[from] std::io::Error),
73
74    #[error("Formatting error: {0}")]
75    Fmt(#[from] std::fmt::Error),
76}
77
78impl RadiateError {
79    pub fn code(&self) -> Code {
80        match self {
81            RadiateError::Builder(_) => Code::Builder,
82            RadiateError::Engine(_) => Code::Engine,
83            RadiateError::Step(_) => Code::Step,
84            RadiateError::Genome(_) => Code::Genome,
85            RadiateError::Metric(_) => Code::Metric,
86            RadiateError::Expr(_) => Code::Expr,
87            RadiateError::Fitness(_) => Code::Fitness,
88            RadiateError::AnyValue(_) => Code::AnyValue,
89            RadiateError::Other(_) => Code::Other,
90            #[cfg(feature = "python")]
91            RadiateError::Python(_) => Code::Python,
92            RadiateError::Multiple(_) => Code::Multiple,
93            RadiateError::Context { .. } => Code::Context,
94            RadiateError::IO(_) => Code::IO,
95            RadiateError::Fmt(_) => Code::Other,
96        }
97    }
98    pub fn context(self, msg: impl Into<String>) -> Self {
99        RadiateError::Context {
100            context: msg.into(),
101            source: Box::new(self),
102        }
103    }
104
105    /// The `Code` of the innermost cause, seeing through `Context` wrappers.
106    ///
107    /// `code()` reports `Context` for any error given context, which loses the
108    /// original classification. This walks to the root cause so callers (e.g.
109    /// the `PyErr` conversion) can decide how to surface the error based on what
110    /// actually went wrong, not on whether context happened to be attached.
111    pub fn leaf_code(&self) -> Code {
112        match self {
113            RadiateError::Context { source, .. } => source.leaf_code(),
114            other => other.code(),
115        }
116    }
117}
118
119pub trait ResultExt<T> {
120    fn context(self, msg: impl Into<String>) -> Result<T>;
121    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T>;
122}
123
124impl<T, E: Into<RadiateError>> ResultExt<T> for std::result::Result<T, E> {
125    fn context(self, msg: impl Into<String>) -> Result<T> {
126        self.map_err(|e| e.into().context(msg))
127    }
128
129    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T> {
130        self.map_err(|e| e.into().context(f()))
131    }
132}
133
134#[doc(hidden)]
135pub mod __private {
136    #[inline]
137    #[cold]
138    #[must_use]
139    pub fn must_use<E>(e: E) -> E {
140        e
141    }
142}
143
144#[macro_export]
145macro_rules! radiate_err {
146    // Formatted message
147    (Builder: $fmt:literal $(, $arg:expr)* $(,)?) => {
148        $crate::__private::must_use($crate::RadiateError::Builder(format!($fmt, $($arg),*)))
149    };
150    (Engine: $fmt:literal $(, $arg:expr)* $(,)?) => {
151        $crate::__private::must_use($crate::RadiateError::Engine(format!($fmt, $($arg),*)))
152    };
153    (Step: $step:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
154        $crate::__private::must_use($crate::RadiateError::Step(format!($fmt, $($arg),*)))
155    };
156    (Genome: $fmt:literal $(, $arg:expr)* $(,)?) => {
157        $crate::__private::must_use($crate::RadiateError::Genome(format!($fmt, $($arg),*)))
158    };
159    (Fitness: $fmt:literal $(, $arg:expr)* $(,)?) => {
160        $crate::__private::must_use($crate::RadiateError::Fitness(format!($fmt, $($arg),*)))
161    };
162    (Python: $fmt:literal $(, $arg:expr)* $(,)?) => {
163        $crate::__private::must_use(pyo3::PyErr::new::<pyo3::exceptions::PyException, _>(format!($fmt, $($arg),*)))
164    };
165    (Metric: $fmt:literal $(, $arg:expr)* $(,)?) => {
166        $crate::__private::must_use($crate::RadiateError::Metric(format!($fmt, $($arg),*)))
167    };
168    (Expr: $fmt:literal $(, $arg:expr)* $(,)?) => {
169        $crate::__private::must_use($crate::RadiateError::Expr(format!($fmt, $($arg),*)))
170    };
171    (AnyValue: $fmt:literal $(, $arg:expr)* $(,)?) => {
172        $crate::__private::must_use($crate::RadiateError::AnyValue(format!($fmt, $($arg),*)))
173    };
174
175    // Contextual message
176    (Context: $msg:expr, $source:expr $(,)?) => {
177        $crate::__private::must_use($source.into().context($msg))
178    };
179
180    (IO: $fmt:literal $(, $arg:expr)* $(,)?) => {
181        $crate::__private::must_use($crate::RadiateError::IO(format!($fmt, $($arg),*)))
182    };
183    (Fmt: $fmt:literal $(, $arg:expr)* $(,)?) => {
184        $crate::__private::must_use($crate::RadiateError::Fmt(format!($fmt, $($arg),*)))
185    };
186
187    // Raw string-like message (any expr -> String)
188    (Builder: $msg:expr $(,)?) => {
189        $crate::__private::must_use($crate::RadiateError::Builder($msg.to_string()))
190    };
191    (Engine: $msg:expr $(,)?) => {
192        $crate::__private::must_use($crate::RadiateError::Engine($msg.to_string()))
193    };
194    (Genome: $msg:expr $(,)?) => {
195        $crate::__private::must_use($crate::RadiateError::Genome($msg.to_string()))
196    };
197    (Fitness: $msg:expr $(,)?) => {
198        $crate::__private::must_use($crate::RadiateError::Fitness($msg.to_string()))
199    };
200    (Python: $msg:expr $(,)?) => {
201        $crate::__private::must_use(pyo3::PyErr::new::<pyo3::exceptions::PyException, _>($msg.to_string()))
202    };
203    (Metric: $msg:expr $(,)?) => {
204        $crate::__private::must_use($crate::RadiateError::Metric($msg.to_string()))
205    };
206    (Expr: $msg:expr $(,)?) => {
207        $crate::__private::must_use($crate::RadiateError::Expr($msg.to_string()))
208    };
209    (IO: $msg:expr $(,)?) => {
210        $crate::__private::must_use($crate::RadiateError::IO($msg.to_string()))
211    };
212    (Fmt: $msg:expr $(,)?) => {
213        $crate::__private::must_use($crate::RadiateError::Fmt($msg.to_string()))
214    };
215
216    // Fallback -> Engine (for now, could be Metric or other)
217    ($msg:expr $(,)?) => {
218        $crate::__private::must_use($crate::RadiateError::Engine($msg.to_string()))
219    };
220}
221
222#[macro_export]
223macro_rules! radiate_bail {
224    ($($tt:tt)+) => { return Err($crate::radiate_err!($($tt)+)) };
225}
226
227#[macro_export]
228macro_rules! ensure {
229    ($cond:expr, $($tt:tt)+) => {
230        if !$cond { $crate::radiate_bail!($($tt)+); }
231    };
232}