Skip to main content

bevy_ecs/error/
bevy_error.rs

1use alloc::{borrow::Cow, boxed::Box};
2use bevy_platform::cell::SyncCell;
3use core::{
4    any::Any,
5    error::Error,
6    fmt::{Debug, Display},
7};
8
9/// The built in "universal" Bevy error type. This has a blanket [`From`] impl for any type that implements Rust's [`Error`],
10/// meaning it can be used as a "catch all" error.
11///
12/// # Severity
13///
14/// Each [`BevyError`] carries a [`Severity`] value that indicates how serious the error is.
15/// While the levels within [`Severity`] correspond to traditional logging levels,
16/// these levels are fundamentally advisory metadata.
17/// The fallback error handler ultimately has discretion to respond to each of these errors
18/// according to its configuration.
19/// The error handler ultimately has discretion to respond to each of these errors according to its configuration.
20/// You can change the behavior of the fallback handler by modifying the [`FallbackErrorHandler`] resource.
21///
22/// By default, errors without an assigned severity use [`Severity::Panic`], and will cause your application to panic.
23/// You can change the severity of an error by using [`with_severity`], or [`map_severity`] on any [`Result`] type.
24///
25/// [`FallbackErrorHandler`]: crate::error::handler::FallbackErrorHandler
26/// [`with_severity`]: ResultSeverityExt::with_severity
27/// [`map_severity`]: ResultSeverityExt::map_severity
28///
29/// # Backtraces
30///
31/// When used with the `backtrace` Cargo feature, it can capture a backtrace when the error is constructed (generally in the [`From`] impl).
32///
33/// To enable backtrace capture on supported platforms,
34/// set the `RUST_BACKTRACE` environment variable.
35/// See [`Backtrace::capture`] for details.
36///
37/// When the error is printed, the backtrace will be displayed.
38/// By default, the backtrace will be trimmed down to filter out noise.
39/// To see the full backtrace, set the `BEVY_BACKTRACE=full` environment variable.
40///
41/// [`Backtrace::capture`]: https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html#method.capture
42///
43/// # Context
44///
45/// You can attach a context message to a [`Result`] or [`Option`] value to turn it into
46/// a [`Result`] with a [`BevyError`] using [`context`] or [`with_context`].
47/// The resulting error will have the message passed to [`context`] added to it.
48///
49/// [`context`]: ContextExt::context
50/// [`with_context`]: ContextExt::with_context
51///
52/// # Usage
53///
54/// ```
55/// # use bevy_ecs::prelude::*;
56///
57/// fn fallible_system() -> Result<(), BevyError> {
58///     // This will result in Rust's built-in ParseIntError, which will automatically
59///     // be converted into a BevyError with an additional message.
60///     let parsed: usize = "I am not a number".parse().context("failed to parse number")?;
61///     Ok(())
62/// }
63/// ```
64pub struct BevyError {
65    inner: Box<InnerBevyError>,
66}
67
68impl BevyError {
69    /// Constructs a new [`BevyError`] with the given [`Severity`].
70    ///
71    /// The error will be stored as a `Box<dyn Error + Send + Sync>`.
72    ///
73    /// The easiest way to use this is to pass in a string.
74    /// This works because any type that can be converted into a `Box<dyn Error + Send + Sync>` can be used,
75    /// and [`str`] is one such type.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// # use bevy_ecs::error::{BevyError, Severity};
81    ///
82    /// fn some_function(val: i64) -> Result<(), BevyError> {
83    ///     if val < 0 {
84    ///         // Consider using the bevy_error! or even the bail! macro for format! support
85    ///         let error =
86    ///             BevyError::new(Severity::Panic, format!("Value can't be negative {val}"));
87    ///         return Err(error);
88    ///     }
89    ///
90    ///     // ...
91    ///     Ok(())
92    /// }
93    /// ```
94    pub fn new<E>(severity: Severity, error: E) -> Self
95    where
96        Box<dyn Error + Sync + Send>: From<E>,
97    {
98        Self::from(error).with_severity(severity)
99    }
100
101    /// Constructs a new [`BevyError`] with the given [`Severity`].
102    ///
103    /// Like [`BevyError::new`], but if the `backtrace` cargo feature is enabled
104    /// it will use the supplied backtrace instead of capturing a new one.
105    #[cfg(feature = "std")]
106    pub fn new_with_backtrace<E>(
107        severity: Severity,
108        error: E,
109        backtrace: std::backtrace::Backtrace,
110    ) -> Self
111    where
112        Box<dyn Error + Sync + Send>: From<E>,
113    {
114        #[cfg(not(feature = "backtrace"))]
115        drop(backtrace);
116        BevyError {
117            inner: Box::new(InnerBevyError {
118                error: error.into(),
119                severity,
120                context: alloc::vec![],
121                panic_payload: None,
122                #[cfg(feature = "backtrace")]
123                backtrace,
124            }),
125        }
126    }
127
128    /// Creates a new [`BevyError`] with the [`Severity::Ignore`] severity.
129    ///
130    /// This is a shorthand for <code>[BevyError::new(Severity::Ignore, error)](BevyError::new)</code>.
131    pub fn ignore<E>(error: E) -> Self
132    where
133        Box<dyn Error + Send + Sync>: From<E>,
134    {
135        Self::new(Severity::Ignore, error)
136    }
137
138    /// Creates a new [`BevyError`] with the [`Severity::Trace`] severity.
139    ///
140    /// This is a shorthand for <code>[BevyError::new(Severity::Trace, error)](BevyError::new)</code>.
141    pub fn trace<E>(error: E) -> Self
142    where
143        Box<dyn Error + Send + Sync>: From<E>,
144    {
145        Self::new(Severity::Trace, error)
146    }
147
148    /// Creates a new [`BevyError`] with the [`Severity::Debug`] severity.
149    ///
150    /// This is a shorthand for <code>[BevyError::new(Severity::Debug, error)](BevyError::new)</code>.
151    pub fn debug<E>(error: E) -> Self
152    where
153        Box<dyn Error + Send + Sync>: From<E>,
154    {
155        Self::new(Severity::Debug, error)
156    }
157
158    /// Creates a new [`BevyError`] with the [`Severity::Info`] severity.
159    ///
160    /// This is a shorthand for <code>[BevyError::new(Severity::Info, error)](BevyError::new)</code>.
161    pub fn info<E>(error: E) -> Self
162    where
163        Box<dyn Error + Send + Sync>: From<E>,
164    {
165        Self::new(Severity::Info, error)
166    }
167
168    /// Creates a new [`BevyError`] with the [`Severity::Warning`] severity.
169    ///
170    /// This is a shorthand for <code>[BevyError::new(Severity::Warning, error)](BevyError::new)</code>.
171    pub fn warning<E>(error: E) -> Self
172    where
173        Box<dyn Error + Send + Sync>: From<E>,
174    {
175        Self::new(Severity::Warning, error)
176    }
177
178    /// Creates a new [`BevyError`] with the [`Severity::Error`] severity.
179    ///
180    /// This is a shorthand for <code>[BevyError::new(Severity::Error, error)](BevyError::new)</code>.
181    pub fn error<E>(error: E) -> Self
182    where
183        Box<dyn Error + Send + Sync>: From<E>,
184    {
185        Self::new(Severity::Error, error)
186    }
187
188    /// Creates a new [`BevyError`] with the [`Severity::Panic`] severity.
189    ///
190    /// This is a shorthand for <code>[BevyError::new(Severity::Panic, error)](BevyError::new)</code>.
191    pub fn panic<E>(error: E, payload: Box<dyn Any + Send>) -> Self
192    where
193        Box<dyn Error + Send + Sync>: From<E>,
194    {
195        Self::new(Severity::Panic, error).with_payload(payload)
196    }
197
198    /// Checks if the internal error is of the given type.
199    pub fn is<E: Error + 'static>(&self) -> bool {
200        self.inner.error.is::<E>()
201    }
202
203    /// Attempts to downcast the internal error to the given type.
204    pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
205        self.inner.error.downcast_ref::<E>()
206    }
207
208    fn format_backtrace(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209        #[cfg(feature = "backtrace")]
210        {
211            let f = _f;
212            let backtrace = &self.inner.backtrace;
213            if let std::backtrace::BacktraceStatus::Captured = backtrace.status() {
214                // TODO: Cache
215                let full_backtrace = std::env::var("BEVY_BACKTRACE").is_ok_and(|val| val == "full");
216
217                let backtrace_str = alloc::string::ToString::to_string(backtrace);
218                let mut skip_next_location_line = false;
219                for line in backtrace_str.split('\n') {
220                    if !full_backtrace {
221                        if skip_next_location_line {
222                            if line.starts_with("             at") {
223                                continue;
224                            }
225                            skip_next_location_line = false;
226                        }
227                        if line.contains("std::backtrace_rs::backtrace::") {
228                            skip_next_location_line = true;
229                            continue;
230                        }
231                        if line.contains(": std::backtrace::Backtrace::")
232                            || line.contains(": <std::backtrace::Backtrace>::")
233                        {
234                            skip_next_location_line = true;
235                            continue;
236                        }
237                        if line.contains("<bevy_ecs::error::bevy_error::BevyError as core::convert::From<E>>::from") {
238                            skip_next_location_line = true;
239                            continue;
240                        }
241                        if line.contains("<core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual") {
242                            skip_next_location_line = true;
243                            continue;
244                        }
245                        if line.contains("__rust_begin_short_backtrace") {
246                            break;
247                        }
248                        if line.contains("bevy_ecs::observer::Observers::invoke::{{closure}}") {
249                            break;
250                        }
251                    }
252                    writeln!(f, "{line}")?;
253                }
254                if !full_backtrace {
255                    if std::thread::panicking() {
256                        SKIP_NORMAL_BACKTRACE.set(true);
257                    }
258                    writeln!(f, "{FILTER_MESSAGE}")?;
259                }
260            }
261        }
262        Ok(())
263    }
264}
265
266/// This type exists (rather than having a `BevyError(Box<dyn InnerBevyError)`) to make [`BevyError`] use a "thin pointer" instead of
267/// a "fat pointer", which reduces the size of our `Result` by a `usize`. This does introduce an extra indirection, but error handling is a "cold path".
268/// We don't need to optimize it to that degree.
269/// PERF: We could probably have the best of both worlds with a "custom vtable" impl, but that's not a huge priority right now and the code simplicity
270/// of the current impl is nice.
271struct InnerBevyError {
272    error: Box<dyn Error + Send + Sync + 'static>,
273    context: alloc::vec::Vec<Cow<'static, str>>,
274    severity: Severity,
275    // The panic payload from `catch_unwind` is a `Box<dyn Any + Send>`. We need `BevyError` to be `Sync` so we store that
276    // in a `SyncCell`. We store it in an `Option` because we ownership of the payload to do things with it.
277    panic_payload: Option<SyncCell<Box<dyn Any + Send>>>,
278    #[cfg(feature = "backtrace")]
279    backtrace: std::backtrace::Backtrace,
280}
281
282/// Indicates how severe a [`BevyError`] is.
283///
284/// These levels correspond to traditional logging levels,
285/// but the severity is advisory metadata used by error handlers to decide how to react (for example: ignore, log, or panic).
286///
287/// To change the behavior of unhandled errors returned from systems,
288/// you can modify the [fallback error handler], and read the [`Severity`] stored inside of each [`BevyError`].
289///
290/// You can change the severity of an error (including assigning an error severity) to an ordinary result
291/// by calling [`with_severity`] or [`map_severity`].
292///
293/// [`with_severity`]: ResultSeverityExt::with_severity
294/// [`map_severity`]: ResultSeverityExt::map_severity
295/// [fallback error handler]: crate::error::handler::FallbackErrorHandler
296#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
297pub enum Severity {
298    /// The error can be safely ignored, and can be completely discarded.
299    Ignore,
300    /// The error can be ignored, unless verbose debugging is required.
301    Trace,
302    /// The error can be safely ignored, but may need to be surfaced during debugging.
303    Debug,
304    /// Nothing has gone wrong, but the error is useful to the user and should be reported.
305    Info,
306    /// Something unexpected but recoverable happened.
307    ///
308    /// Something has probably gone wrong.
309    Warning,
310    /// A real error occurred, but the program may continue.
311    Error,
312    /// A fatal error; the program cannot continue.
313    Panic,
314}
315
316impl BevyError {
317    /// Returns the severity of this error.
318    pub fn severity(&self) -> Severity {
319        self.inner.severity
320    }
321
322    /// Returns this error with its severity overridden.
323    ///
324    /// Note that this doesn't change the underlying error value;
325    /// only the [`Severity`] metadata used by the error handler.
326    pub fn with_severity(mut self, severity: Severity) -> Self {
327        self.inner.severity = severity;
328        self
329    }
330
331    /// Adds a panic payload to the error.
332    /// This allows the panic to be resumed with [`Self::take_payload`] in the error handler.
333    pub fn with_payload(mut self, payload: Box<dyn Any + Send>) -> Self {
334        self.inner.panic_payload = Some(SyncCell::new(payload));
335        self
336    }
337
338    /// Use in an error handler to take the payload and use it to resume unwinding or
339    /// add it to logging.
340    pub fn take_payload(&mut self) -> Option<Box<dyn Any + Send>> {
341        self.inner.panic_payload.take().map(SyncCell::to_inner)
342    }
343}
344
345/// Extension methods for annotating errors with a [`Severity`].
346pub trait ResultSeverityExt<T, E>: Sized {
347    /// Overrides the [`Severity`] of the error if this result is `Err`.
348    /// This does not change control flow; it only annotates the error.
349    ///
350    /// # Example
351    /// ```
352    /// # use bevy_ecs::error::{BevyError, ResultSeverityExt, Severity};
353    /// fn fallible() -> Result<(), BevyError> {
354    ///     // This failure is expected in some contexts, so we downgrade its severity.
355    ///     let _parsed: usize = "I am not a number"
356    ///         .parse()
357    ///         .with_severity(Severity::Warning)?;
358    ///     Ok(())
359    /// }
360    /// ```
361    ///
362    /// For more fine grained control see [`Result::map_severity`](ResultSeverityExt::map_severity)
363    fn with_severity(self, severity: Severity) -> Result<T, BevyError>;
364
365    /// Overrides the [`Severity`] of the error if this result is `Err`.
366    /// This does not change control flow; it only annotates the error.
367    ///
368    /// # Example
369    /// ```
370    /// # use bevy_ecs::error::{BevyError, ResultSeverityExt, Severity};
371    /// # use thiserror::Error;
372    /// # fn validate(_string: &str) -> Result<usize, ValidationError> {
373    /// #     Err(ValidationError::IncorrectVersion)
374    /// # }
375    ///
376    /// #[derive(Error, Debug)]
377    /// pub enum ValidationError {
378    ///     #[error("Incorrect version")]
379    ///     IncorrectVersion,
380    ///     #[error("Syntax error")]
381    ///     SyntaxError,
382    /// }
383    ///
384    /// fn fallible() -> Result<(), BevyError> {
385    ///     // This failure is expected in some contexts, so we downgrade its severity.
386    ///     let _parsed: usize = validate("I am not a number")
387    ///         .map_severity(|e| match e {
388    ///             ValidationError::IncorrectVersion => Severity::Debug,
389    ///             ValidationError::SyntaxError => Severity::Error,
390    ///         })?;
391    ///     Ok(())
392    /// }
393    /// ```
394    ///
395    /// If you don't need to inspect the error, use [`Result::with_severity`](ResultSeverityExt::with_severity)
396    fn map_severity(self, f: impl FnOnce(&E) -> Severity) -> Result<T, BevyError>;
397
398    /// Overrides the severity of the error with [`Severity::Ignore`]. See [`Result::with_severity`]
399    ///
400    /// This is shorthand for `self.with_severity(Severity::Ignore)`
401    fn ignore(self) -> Result<T, BevyError> {
402        self.with_severity(Severity::Ignore)
403    }
404
405    /// Overrides the severity of the error with [`Severity::Trace`]. See [`Result::with_severity`]
406    ///
407    /// This is shorthand for `self.with_severity(Severity::Trace)`
408    fn trace(self) -> Result<T, BevyError> {
409        self.with_severity(Severity::Trace)
410    }
411
412    /// Overrides the severity of the error with [`Severity::Info`]. See [`Result::with_severity`]
413    ///
414    /// This is shorthand for `self.with_severity(Severity::Info)`
415    fn info(self) -> Result<T, BevyError> {
416        self.with_severity(Severity::Info)
417    }
418
419    /// Overrides the severity of the error with [`Severity::Warning`]. See [`Result::with_severity`]
420    ///
421    /// This is shorthand for `self.with_severity(Severity::Warning)`
422    fn warn(self) -> Result<T, BevyError> {
423        self.with_severity(Severity::Warning)
424    }
425
426    /// Overrides the severity of the error with [`Severity::Error`]. See [`Result::with_severity`]
427    ///
428    /// This is shorthand for `self.with_severity(Severity::Error)`
429    fn error(self) -> Result<T, BevyError> {
430        self.with_severity(Severity::Error)
431    }
432
433    /// Overrides the severity of the error with [`Severity::Panic`]. See [`Result::with_severity`]
434    ///
435    /// This is shorthand for `self.with_severity(Severity::Panic)`
436    fn panic(self) -> Result<T, BevyError> {
437        self.with_severity(Severity::Panic)
438    }
439}
440
441impl<T, E> ResultSeverityExt<T, E> for Result<T, E>
442where
443    E: Into<BevyError>,
444{
445    fn with_severity(self, severity: Severity) -> Result<T, BevyError> {
446        self.map_err(|e| e.into().with_severity(severity))
447    }
448
449    fn map_severity(self, f: impl FnOnce(&E) -> Severity) -> Result<T, BevyError> {
450        self.map_err(|e| {
451            let severity = f(&e);
452            e.into().with_severity(severity)
453        })
454    }
455}
456
457/// Extension methods for adding additional context messages to a [`BevyError`]
458pub trait ContextExt<T>: Sized {
459    /// Annotate the error with a context message.
460    ///
461    /// # Example
462    /// ```
463    /// # use bevy_ecs::error::{BevyError, ContextExt};
464    /// fn fallible() -> Result<(), BevyError> {
465    ///     // Produces a `BevyError` with the message
466    ///     // "failed to parse number: invalid digit found in string"
467    ///     let _parsed: usize = "I am not a number"
468    ///         .parse()
469    ///         .context("failed to parse number")?;
470    ///
471    ///     Ok(())
472    /// }
473    /// ```
474    fn context<C>(self, context: C) -> Result<T, BevyError>
475    where
476        C: Into<Cow<'static, str>>,
477    {
478        self.with_context(move || context)
479    }
480
481    /// Annotate the error with a context message from a closure
482    ///
483    /// # Example
484    /// ```
485    /// # use bevy_ecs::error::{BevyError, ContextExt};
486    /// # use std::fs;
487    /// fn fallible() -> Result<(), BevyError> {
488    ///     let path = "some_file.txt";
489    ///     let _message = fs::read_to_string(path)
490    ///         .with_context(|| format!("failed to read {path}"))?;
491    ///
492    ///     Ok(())
493    /// }
494    /// ```
495    fn with_context<C>(self, context: impl FnOnce() -> C) -> Result<T, BevyError>
496    where
497        C: Into<Cow<'static, str>>;
498}
499impl<T, E> ContextExt<T> for Result<T, E>
500where
501    E: Into<BevyError>,
502{
503    fn with_context<C>(self, context: impl FnOnce() -> C) -> Result<T, BevyError>
504    where
505        C: Into<Cow<'static, str>>,
506    {
507        match self {
508            Ok(v) => Ok(v),
509            Err(error) => {
510                let mut error = error.into();
511                let message = context().into();
512                error.inner.context.push(message);
513                Err(error)
514            }
515        }
516    }
517}
518
519impl<T> ContextExt<T> for Option<T> {
520    fn with_context<C>(self, context: impl FnOnce() -> C) -> Result<T, BevyError>
521    where
522        C: Into<Cow<'static, str>>,
523    {
524        match self {
525            Some(v) => Ok(v),
526            None => {
527                let message = context().into();
528
529                Err(message.into())
530            }
531        }
532    }
533}
534
535// NOTE: writing the impl this way gives us From<&str> ... nice!
536impl<E> From<E> for BevyError
537where
538    Box<dyn Error + Send + Sync + 'static>: From<E>,
539{
540    #[cold]
541    fn from(error: E) -> Self {
542        BevyError {
543            inner: Box::new(InnerBevyError {
544                error: error.into(),
545                severity: Severity::Panic,
546                context: alloc::vec![],
547                panic_payload: None,
548                #[cfg(feature = "backtrace")]
549                backtrace: std::backtrace::Backtrace::capture(),
550            }),
551        }
552    }
553}
554
555impl Display for BevyError {
556    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
557        match &self.inner.context {
558            context if context.is_empty() => writeln!(f, "{}", self.inner.error)?,
559            context if context.len() == 1 => {
560                writeln!(f, "{}: {}", context[0].trim(), self.inner.error)?;
561            }
562            context => {
563                // The most recent message is the last one in the `Vec`
564                // so we need to reverse the iterator
565                let mut reversed = context.iter().rev();
566                let first = reversed.next().unwrap().trim();
567
568                writeln!(f, "{first}\n\nCaused by:")?;
569                for message in reversed {
570                    let message = message.trim();
571                    writeln!(f, "\t{message}")?;
572                }
573                writeln!(f, "\t{}", self.inner.error)?;
574            }
575        }
576        self.format_backtrace(f)?;
577        Ok(())
578    }
579}
580
581impl Debug for BevyError {
582    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
583        writeln!(f, "{:?}", self.inner.error)?;
584        if !self.inner.context.is_empty() {
585            writeln!(f, "context: {:?}", self.inner.context)?;
586        }
587        self.format_backtrace(f)?;
588        Ok(())
589    }
590}
591
592#[cfg(feature = "backtrace")]
593const FILTER_MESSAGE: &str = "note: Some \"noisy\" backtrace lines have been filtered out. Run with `BEVY_BACKTRACE=full` for a verbose backtrace.";
594
595#[cfg(feature = "backtrace")]
596std::thread_local! {
597    static SKIP_NORMAL_BACKTRACE: core::cell::Cell<bool> =
598        const { core::cell::Cell::new(false) };
599}
600
601/// When called, this will skip the currently configured panic hook when a [`BevyError`] backtrace has already been printed.
602#[cfg(feature = "backtrace")]
603#[expect(clippy::print_stdout, reason = "Allowed behind `std` feature gate.")]
604pub fn bevy_error_panic_hook(
605    current_hook: impl Fn(&std::panic::PanicHookInfo),
606) -> impl Fn(&std::panic::PanicHookInfo) {
607    move |info| {
608        if SKIP_NORMAL_BACKTRACE.replace(false) {
609            if let Some(payload) = info.payload_as_str() {
610                std::println!("{payload}");
611            }
612            return;
613        }
614
615        current_hook(info);
616    }
617}
618
619/// Creates a new [`BevyError`] from a string.
620///
621/// Strings can be formatted like the [`format!`](std::format!) macro. Severity
622/// can optionally be provided to change it from the default [`Severity::Panic`].
623/// This can be done by adding the severity as the fist argument.
624///
625/// # Example
626/// ```
627/// use bevy_ecs::{bevy_error, error::{BevyError, Severity}};
628///
629/// fn this_will_fail(value: u32) -> Result<(), BevyError> {
630///     if value == 0 {
631///         return Err(bevy_error!(Severity::Debug, "A debug message"));
632///     } else {
633///         return Err(bevy_error!("We can even do formatting {value}, {}", "hello"));
634///     }
635/// }
636/// ```
637#[macro_export]
638macro_rules! bevy_error {
639    ($fmt:literal) => {
640        $crate::error::BevyError::new($crate::error::Severity::Panic, $fmt)
641    };
642    ($fmt:literal, $($arg:tt)*) => {
643        $crate::error::BevyError::new($crate::error::Severity::Panic, $crate::__macro_exports::format!($fmt, $($arg)*))
644    };
645    ($severity:expr, $fmt:literal) => {
646        $crate::error::BevyError::new($severity, $fmt)
647    };
648    ($severity:expr, $fmt:literal, $($arg:tt)*) => {
649        $crate::error::BevyError::new($severity, $crate::__macro_exports::format!($fmt, $($arg)*))
650    };
651    ($severity:expr) => {
652        compile_error!("missing error message")
653    };
654}
655
656/// Returns early with an error.
657///
658/// Equivalent to <code>return Err([bevy_error!(\...)](bevy_error!))</code>
659/// As a result the returned error defaults to [`Severity::Panic`]. As with
660/// `bevy_error!` the severity can be changed by providing a severity as the
661/// first argument. To return early only when a condition is false, use
662/// [`ensure!`](crate::ensure!).
663///
664/// # Example
665/// ```
666/// use bevy_ecs::{bail, error::{BevyError, Severity}};
667///
668/// fn do_some_stuff(val: i32) -> Result<(), BevyError> {
669///     if val < 0 {
670///         bail!(Severity::Warning, "Something is broken: {}", val);
671///     } else if val == 0 {
672///         bail!("Value really can't be zero");
673///     }
674///
675///     // ...
676///     Ok(())
677/// }
678/// ```
679#[macro_export]
680macro_rules! bail {
681    ($($args:tt)+) => {
682        return core::result::Result::Err($crate::bevy_error!($($args)*))
683    };
684}
685
686/// Returns early with an error if a condition is false.
687///
688/// Equivalent to <code>if !condition { [bail!](bail!)(\...) }</code>. As with
689/// [`bail!`], the returned error defaults to [`Severity::Panic`], and the
690/// severity can be changed by providing it after the condition.
691///
692/// # Example
693/// ```
694/// use bevy_ecs::{ensure, error::{BevyError, Severity}};
695///
696/// fn validate_score(score: i32) -> Result<(), BevyError> {
697///     ensure!(score >= 0, "score must not be negative: {}", score);
698///     ensure!(score <= 100, Severity::Warning, "score is too high: {}", score);
699///     Ok(())
700/// }
701/// ```
702#[macro_export]
703macro_rules! ensure {
704    ($condition:expr, $($args:tt)+) => {
705        if !$condition {
706            $crate::bail!($($args)*);
707        }
708    };
709}
710
711#[cfg(test)]
712mod tests {
713    use crate::error::BevyError;
714    use crate::error::ContextExt;
715    use alloc::string::ToString;
716
717    #[test]
718    #[cfg(not(miri))] // miri backtraces are weird
719    #[cfg(not(windows))] // the windows backtrace in this context is ... unhelpful and not worth testing
720    fn filtered_backtrace_test() {
721        fn i_fail() -> crate::error::Result {
722            let _: usize = "I am not a number".parse()?;
723            Ok(())
724        }
725
726        let capture_backtrace = std::env::var_os("RUST_BACKTRACE");
727
728        if capture_backtrace.is_none() || capture_backtrace.clone().is_some_and(|s| s == "0") {
729            panic!("This test only works if rust backtraces are enabled. Value set was {capture_backtrace:?}. Please set RUST_BACKTRACE to any value other than 0 and run again.")
730        }
731
732        let error = i_fail().err().unwrap();
733        let debug_message = alloc::format!("{error:?}");
734        let mut lines = debug_message.lines().peekable();
735        assert_eq!(
736            "ParseIntError { kind: InvalidDigit }",
737            lines.next().unwrap()
738        );
739
740        // On mac backtraces can start with Backtrace::create
741        // Rust 1.95 changed the format to use angle brackets: <std::backtrace::Backtrace>::create
742        // Rust 1.98 stopped inlining create into capture, so more than one of these frames can appear
743        while lines.peek().is_some_and(|line| {
744            let symbol = line.get(6..).unwrap_or("");
745            symbol.starts_with("std::backtrace::Backtrace::")
746                || symbol.starts_with("<std::backtrace::Backtrace>::")
747        }) {
748            lines.next().unwrap();
749        }
750
751        let expected_lines = alloc::vec![
752            "<bevy_ecs::error::bevy_error::BevyError as core::convert::From<core::num::error::ParseIntError>>::from",
753            "<core::result::Result<(), bevy_ecs::error::bevy_error::BevyError> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible, core::num::error::ParseIntError>>>::from_residual",
754            "bevy_ecs::error::bevy_error::tests::filtered_backtrace_test::i_fail",
755            "bevy_ecs::error::bevy_error::tests::filtered_backtrace_test",
756            "bevy_ecs::error::bevy_error::tests::filtered_backtrace_test::{closure#0}",
757            "<bevy_ecs::error::bevy_error::tests::filtered_backtrace_test::{closure#0} as core::ops::function::FnOnce<()>>::call_once",
758        ];
759
760        for expected in expected_lines {
761            // On mac, it can sometimes start with an "at" line
762            let mut skip = false;
763            if let Some(line) = lines.peek()
764                && line.starts_with("             at")
765            {
766                skip = true;
767            }
768
769            if skip {
770                lines.next().unwrap();
771            }
772
773            let line = lines.next().unwrap();
774            assert_eq!(&line[6..], expected);
775        }
776        // To handle any potential "at" line after the expected lines
777        let mut skip = false;
778        if let Some(line) = lines.peek()
779            && line.starts_with("             at")
780        {
781            skip = true;
782        }
783
784        if skip {
785            lines.next().unwrap();
786        }
787
788        // on linux there is a second call_once
789        let mut skip = false;
790        if let Some(line) = lines.peek()
791            && line.get(6..) == Some("<fn() -> core::result::Result<(), alloc::string::String> as core::ops::function::FnOnce<()>>::call_once")
792        {
793            skip = true;
794        }
795
796        if skip {
797            lines.next().unwrap();
798        }
799        let mut skip = false;
800        if let Some(line) = lines.peek()
801            && line.starts_with("             at")
802        {
803            skip = true;
804        }
805
806        if skip {
807            lines.next().unwrap();
808        }
809        assert_eq!(super::FILTER_MESSAGE, lines.next().unwrap());
810        assert!(lines.next().is_none());
811    }
812
813    #[test]
814    fn downcasting() {
815        #[derive(Debug, PartialEq)]
816        struct Fun(i32);
817
818        impl core::fmt::Display for Fun {
819            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
820                core::fmt::Debug::fmt(&self, f)
821            }
822        }
823        impl core::error::Error for Fun {}
824
825        let new_error = BevyError::new(crate::error::Severity::Debug, Fun(1));
826
827        assert!(new_error.is::<Fun>());
828        assert_eq!(new_error.downcast_ref::<Fun>(), Some(&Fun(1)));
829    }
830
831    /// Testing the functionality would be difficult so we at least check if it
832    /// compiles.
833    #[test]
834    fn bevy_error_macro() {
835        bevy_error!("One arg");
836        bevy_error!(crate::error::Severity::Debug, "With severity");
837        bevy_error!(
838            crate::error::Severity::Debug,
839            "With severity and args {}",
840            4 / 3
841        );
842
843        // This is the pain in the ass one since both args are literals but neither is severity
844        bevy_error!("Format string {}", 1 + 2);
845    }
846
847    #[test]
848    fn bevy_bail_macro() {
849        // Simplest way to specify the return type
850        fn t(f: impl Fn() -> Result<(), BevyError>) {
851            let val = f();
852
853            assert!(val.is_err(), "expected error got {:?}", val);
854        }
855
856        t(|| bail!("One arg"));
857        t(|| bail!(crate::error::Severity::Debug, "With severity"));
858        t(|| {
859            bail!(
860                crate::error::Severity::Debug,
861                "With severity and args {}",
862                2
863            )
864        });
865        t(|| bail!("Format string {}", 1 + 2));
866    }
867
868    #[test]
869    fn bevy_ensure_macro() {
870        fn validate(value: i32) -> Result<(), BevyError> {
871            ensure!(value != 0, "value must not be zero");
872            ensure!(
873                value > 0,
874                crate::error::Severity::Warning,
875                "value must be positive: {}",
876                value
877            );
878            Ok(())
879        }
880
881        assert!(validate(1).is_ok());
882
883        let zero = validate(0).unwrap_err();
884        assert_eq!(zero.severity(), crate::error::Severity::Panic);
885        assert!(zero.to_string().starts_with("value must not be zero"));
886
887        let negative = validate(-1).unwrap_err();
888        assert_eq!(negative.severity(), crate::error::Severity::Warning);
889        assert!(negative
890            .to_string()
891            .starts_with("value must be positive: -1"));
892    }
893
894    #[test]
895    fn context() {
896        let empty = None::<i32>;
897        let as_result = empty.context("Didn't have anything!");
898        assert!(as_result
899            .unwrap_err()
900            .to_string()
901            .starts_with("Didn't have anything!\n"));
902
903        let err: Result<i32, BevyError> =
904            Err(BevyError::new(crate::error::Severity::Debug, "Oh no!"));
905        let mut with_context = err.context("Failed");
906
907        assert!(with_context
908            .as_ref()
909            .unwrap_err()
910            .to_string()
911            .starts_with("Failed: Oh no!\n"));
912
913        with_context = with_context.context("Something went wrong");
914        assert!(with_context.unwrap_err().to_string().starts_with(
915            "Something went wrong
916
917Caused by:
918\tFailed
919\tOh no!
920"
921        ));
922    }
923
924    #[test]
925    fn context_downcasting() {
926        #[derive(Debug, PartialEq)]
927        struct Fun(i32);
928
929        impl core::fmt::Display for Fun {
930            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
931                core::fmt::Debug::fmt(&self, f)
932            }
933        }
934        impl core::error::Error for Fun {}
935
936        let fun: Result<i32, Fun> = Err(Fun(1));
937        let new_error = fun.context("Hello world!");
938
939        assert!(new_error.as_ref().unwrap_err().is::<Fun>());
940        assert_eq!(
941            new_error.as_ref().unwrap_err().downcast_ref::<Fun>(),
942            Some(&Fun(1))
943        );
944
945        let new_new_error = new_error.context("Hey there!");
946
947        assert!(new_new_error.as_ref().unwrap_err().is::<Fun>());
948        assert_eq!(
949            new_new_error.as_ref().unwrap_err().downcast_ref::<Fun>(),
950            Some(&Fun(1))
951        );
952    }
953}