Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_core/src/macros/error.rs

12.4 KiB, 45 runs

created by r1870400018:96, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1#[macro_export]
2/// Create context for an Error.
3///
4///```
5/// use oxedyne_fe2o3_core::prelude::*;
6///
7/// let n = 41;
8/// let result0: Outcome<()> = Err(Error::Local(ErrMsg {
9/// tags: &[ErrTag::Invalid, ErrTag::Input],
10/// msg: errmsg!("The meaning of life is not {}", n),
11/// }));
12///```
13macro_rules! errmsg {
14 () => (
15 format!("{}:{}", file!(), line!())
16 );
17 ($($arg:tt)*) => (
18 format!("{}:{}: {}", file!(), line!(), format!($($arg)*))
19 )
20}
21
22#[macro_export]
23/// Create an Error with context info and tags.
24///
25/// Format: err!(message; tags)
26/// Where message can be:
27/// - A string literal
28/// - A format string with arguments
29/// And compulsory tags are comma-separated ErrTag identifiers
30///
31/// # Examples
32///
33/// ## Local Errors
34/// ```ignore
35/// use oxedyne_fe2o3_core::prelude::*;
36///
37/// // Simple message
38/// let e1 = err!("Just text"; Input);
39///
40/// // Multiple tags
41/// let e2 = err!("Simple message"; Input, Invalid);
42///
43/// // Format string with arguments
44/// let value = 42;
45/// let e3 = err!("Value is {}", value; Input);
46///
47/// // Multiple arguments and tags
48/// let (val1, val2) = (1, 2);
49/// let e4 = err!("Values are {} and {}", val1, val2; Input, Invalid);
50/// ```
51///
52/// ## Upstream Errors
53/// ```ignore
54/// use oxedyne_fe2o3_core::prelude::*;
55/// use std::fs;
56///
57/// let io_error = fs::read_to_string("missing.txt").unwrap_err();
58///
59/// // Simple message
60/// let e1 = err!(io_error, "Failed to read file"; IO, File);
61///
62/// // With format args
63/// let filename = "config.txt";
64/// let e2 = err!(io_error, "Failed to read {}", filename; IO, File);
65/// ```
66macro_rules! err {
67 // Local error with simple message and tags
68 ($msg:expr; $($tag:ident),+) => {
69 Error::Local(ErrMsg {
70 msg: format!("{}:{}: {}", file!(), line!(), $msg),
71 tags: &[$(ErrTag::$tag),+],
72 })
73 };
74
75 // Local error with format string, args and tags
76 ($fmt:literal, $($arg:expr),+; $($tag:ident),+) => {
77 Error::Local(ErrMsg {
78 msg: format!("{}:{}: {}", file!(), line!(), format!($fmt, $($arg),+)),
79 tags: &[$(ErrTag::$tag),+],
80 })
81 };
82
83 // Upstream error with simple message and tags
84 ($err:expr, $msg:expr; $($tag:ident),+) => {
85 Error::Upstream(std::sync::Arc::new($err), ErrMsg {
86 msg: format!("{}:{}: {}", file!(), line!(), $msg),
87 tags: &[$(ErrTag::$tag),+],
88 })
89 };
90
91 // Upstream error with format string, args and tags
92 ($err:expr, $fmt:literal, $($arg:expr),+; $($tag:ident),+) => {
93 Error::Upstream(std::sync::Arc::new($err), ErrMsg {
94 msg: format!("{}:{}: {}", file!(), line!(), format!($fmt, $($arg),+)),
95 tags: &[$(ErrTag::$tag),+],
96 })
97 };
98}
99
100#[macro_export]
101/// A prefix alternative to the `?` operator for error propagation.
102///
103/// This macro provides identical functionality to the `?` operator but uses prefix notation.
104/// It converts errors using the standard `From` trait and propagates them to the caller.
105///
106/// # Examples
107///
108/// Basic usage:
109/// ```ignore
110/// use fe2o3_core::prelude::*;
111/// use std::fs::File;
112///
113/// fn read_file() -> std::io::Result<()> {
114/// let file = ok!(File::create("data.txt"));
115/// Ok(())
116/// }
117/// ```
118///
119/// With different error types:
120/// ```ignore
121/// use fe2o3_core::prelude::*;
122/// use std::error::Error;
123///
124/// fn process_data() -> Result<i32, Box<dyn Error>> {
125/// // Both errors will be converted to Box<dyn Error>
126/// let file = ok!(std::fs::read_to_string("numbers.txt"));
127/// let number = ok!(file.parse::<i32>());
128/// Ok(number)
129/// }
130/// ```
131///
132/// # Performance
133/// This macro has the same performance characteristics as the `?` operator,
134/// as it expands to identical code using the `From` trait for error conversion.
135///
136/// # Note
137/// Unlike `res!` and `catch!`, this macro does not add any context or catch panics.
138/// Use this macro in performance-critical code paths where standard error
139/// propagation is sufficient.
140macro_rules! ok {
141 ($expr:expr) => {
142 ($expr)?
143 };
144}
145
146#[macro_export]
147/// Propagates errors and adds context through error tags while maintaining the error chain.
148///
149/// Similar to `ok!`, but wraps both Rust errors and std error trait objects to add context.
150/// Use this for general application code where error context is valuable.
151///
152/// # Examples
153///
154/// Basic usage with tags:
155/// ```ignore
156/// use fe2o3_core::prelude::*;
157///
158/// fn process_data() -> Outcome<()> {
159/// // Adds IO and Parse tags to any error
160/// let data = res!(read_file(), IO, Parse);
161/// Ok(())
162/// }
163/// ```
164///
165/// Chaining errors (not nested):
166/// ```ignore
167/// let intermediate = res!(first_operation(), IO);
168/// let result = res!(second_operation(intermediate), Processing);
169/// ```
170///
171/// # Note
172/// - Cannot be nested recursively due to return type limitations
173/// - Adds some overhead from Arc and context capture
174/// - For performance-critical code paths, consider using `ok!` instead
175macro_rules! res {
176 ($res:expr, $($etvars:ident),* $(,)?) => {
177 match $res {
178 Ok(v) => v,
179 Err(e) => {
180 return Err(Error::Upstream(std::sync::Arc::new(e), ErrMsg {
181 tags: &[ $(ErrTag::$etvars),* ],
182 msg: errmsg!(),
183 }));
184 },
185 }
186 };
187 ($res:expr, $($enum:ident::$etvars:ident),* $(,)?) => {
188 match $res {
189 Ok(v) => v,
190 Err(e) => {
191 return Err(Error::Upstream(std::sync::Arc::new(e), ErrMsg {
192 tags: &[ $($enum::$etvars),* ],
193 msg: errmsg!(),
194 }));
195 },
196 }
197 };
198 ($res:expr) => {
199 match $res {
200 Ok(v) => v,
201 Err(e) => {
202 return Err(Error::Upstream(std::sync::Arc::new(e), ErrMsg {
203 tags: &[],
204 msg: errmsg!(),
205 }));
206 },
207 }
208 }
209}
210
211#[macro_export]
212/// Propagates errors while catching unwinding panics and adding context.
213///
214/// Most comprehensive error handling macro - converts both errors and unwinding panics
215/// into `Outcome::Err` while maintaining context. Use this at application boundaries
216/// where panic recovery is important.
217///
218/// # Examples
219///
220/// Basic usage:
221/// ```ignore
222/// use fe2o3_core::prelude::*;
223///
224/// fn handle_request() -> Outcome<Response> {
225/// // Will catch panics and convert them to errors
226/// let result = catch!(process_request(), Request, Processing);
227/// Ok(Response::new(result))
228/// }
229/// ```
230///
231/// # Panics
232/// Catches most unwinding panics including:
233/// - Array bounds violations
234/// - Integer overflow in debug builds
235/// - Unwrap/expect failures
236/// - Division by zero
237///
238/// Does not catch:
239/// - Stack overflows
240/// - Memory allocation failures
241/// - Panics in destructors
242/// - FFI panics marked `#[no_unwind]`
243/// - Any panics when compiled with `panic=abort`
244///
245/// # Performance
246/// Has significant overhead due to:
247/// - Unwinding tables in binary
248/// - Stack frame management
249/// - Register state tracking
250///
251/// Use only at key boundaries where panic recovery justifies the cost.
252macro_rules! catch {
253 ($res:expr, $($etvars:ident),* $(,)?) => {
254 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
255 $res
256 })) {
257 Ok(Ok(v)) => v,
258 Ok(Err(e)) => return Err(Error::Upstream(std::sync::Arc::new(e), ErrMsg {
259 tags: &[ $(ErrTag::$etvars),* ],
260 msg: errmsg!(),
261 })),
262 Err(cause) => {
263 let msg = if let Some(s) = cause.downcast_ref::<&str>() {
264 s
265 } else if let Some(s) = cause.downcast_ref::<String>() {
266 s.as_str()
267 } else if let Some(box_any) = cause.downcast_ref::<Box<dyn std::any::Any + Send + Sync>>() {
268 if let Some(string) = box_any.downcast_ref::<String>() {
269 string.as_str()
270 } else {
271 "A panic occurred, but the message is not a string."
272 }
273 } else {
274 "A panic occurred, but the message could not be extracted."
275 };
276 return Err(Error::Local(ErrMsg {
277 tags: &[ ErrTag::Panic, $(ErrTag::$etvars),* ],
278 msg: errmsg!("A panic occurred: {}", msg),
279 }));
280 },
281 }
282 };
283 ($res:expr) => {
284 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
285 $res
286 })) {
287 Ok(Ok(v)) => v,
288 Ok(Err(e)) => {
289 return Err(Error::Upstream(std::sync::Arc::new(e), ErrMsg {
290 tags: &[],
291 msg: errmsg!(),
292 }));
293 },
294 Err(cause) => {
295 let msg = if let Some(s) = cause.downcast_ref::<&str>() {
296 s
297 } else if let Some(s) = cause.downcast_ref::<String>() {
298 s.as_str()
299 } else if let Some(box_any) = cause.downcast_ref::<Box<dyn std::any::Any + Send + Sync>>() {
300 if let Some(string) = box_any.downcast_ref::<String>() {
301 string.as_str()
302 } else {
303 "A panic occurred, but the message is not a string."
304 }
305 } else {
306 "A panic occurred, but the message could not be extracted."
307 };
308 return Err(Error::Local(ErrMsg {
309 tags: &[ ErrTag::Panic ],
310 msg: errmsg!("A panic occurred: {}", msg),
311 }));
312 },
313 }
314 }
315}
316
317#[macro_export]
318/// While `catch!` can handle any error type that implements `std::error::Error`, this macro deals
319/// with cases like `anyhow::Error`, which do not. It can be difficult or impossible to get the
320/// error out as a `std::error::Error` so we just use the `String`.
321macro_rules! catch_other {
322 ($res:expr, $($etvars:ident),* $(,)?) => {
323 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
324 $res
325 })) {
326 Ok(Ok(v)) => v,
327 Ok(Err(e)) => return Err(Error::Other(
328 ErrMsg {
329 tags: &[ErrTag::Upstream],
330 msg: e.to_string(),
331 }
332 )),
333 Err(cause) => {
334 let msg = if let Some(s) = cause.downcast_ref::<&str>() {
335 s
336 } else if let Some(s) = cause.downcast_ref::<String>() {
337 s.as_str()
338 } else if let Some(box_any) = cause.downcast_ref::<Box<dyn std::any::Any + Send + Sync>>() {
339 if let Some(string) = box_any.downcast_ref::<String>() {
340 string.as_str()
341 } else {
342 "A panic occurred, but the message is not a string."
343 }
344 } else {
345 "A panic occurred, but the message could not be extracted."
346 };
347 return Err(Error::Local(ErrMsg {
348 tags: &[ ErrTag::Panic, $(ErrTag::$etvars),* ],
349 msg: errmsg!("A panic occurred: {}", msg),
350 }));
351 },
352 }
353 };
354 ($res:expr) => {
355 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
356 $res
357 })) {
358 Ok(Ok(v)) => v,
359 Ok(Err(e)) => return Err(Error::Other(
360 ErrMsg {
361 tags: &[ErrTag::Upstream],
362 msg: e.to_string(),
363 }
364 )),
365 Err(cause) => {
366 let msg = if let Some(s) = cause.downcast_ref::<&str>() {
367 s
368 } else if let Some(s) = cause.downcast_ref::<String>() {
369 s.as_str()
370 } else if let Some(box_any) = cause.downcast_ref::<Box<dyn std::any::Any + Send + Sync>>() {
371 if let Some(string) = box_any.downcast_ref::<String>() {
372 string.as_str()
373 } else {
374 "A panic occurred, but the message is not a string."
375 }
376 } else {
377 "A panic occurred, but the message could not be extracted."
378 };
379 return Err(Error::Local(ErrMsg {
380 tags: &[ ErrTag::Panic ],
381 msg: errmsg!("A panic occurred: {}", msg),
382 }));
383 },
384 }
385 }
386}