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