Skip to content

Commit a5cbc9e

Browse files
loreballalice-i-cecilebenfrankel
authored
Add new and severity based constructors to BevyError (#23684)
# Objective Add constructors for making `BevyError`s with a specific severity. Closes #23676. ## Solution Add a `new` constructor plus 1 constructor for every possible `Severity`. ## Testing My eyes and a simple test case that constructs an error and tests if the downcasting works. --- ## Showcase A `BevyError` now has multiple constructor to create one with an expected severity ```rust use bevy::ecs::error::{BevyError, Severity}; let debug_error = BevyError::new(Severity::Debug, "This works with strings"); let warn_error = BevyError::warn("There's a constructor for each severity level"); ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
1 parent c981ecd commit a5cbc9e

2 files changed

Lines changed: 133 additions & 4 deletions

File tree

crates/bevy_ecs/src/error/bevy_error.rs

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,112 @@ pub struct BevyError {
5454
}
5555

5656
impl BevyError {
57+
/// Constructs a new [`BevyError`] with the given [`Severity`].
58+
///
59+
/// The error will be stored as a `Box<dyn Error + Send + Sync>`.
60+
///
61+
/// The easiest way to use this is to pass in a string.
62+
/// This works because any type that can be converted into a `Box<dyn Error + Send + Sync>` can be used,
63+
/// and [`str`] is one such type.
64+
///
65+
/// # Examples
66+
///
67+
/// ```
68+
/// # use bevy_ecs::error::{BevyError, Severity};
69+
///
70+
/// fn some_function(val: i64) -> Result<(), BevyError> {
71+
/// if val < 0 {
72+
/// let error =
73+
/// BevyError::new(Severity::Panic, format!("Value can't be negative {val}"));
74+
/// return Err(error);
75+
/// }
76+
///
77+
/// // ...
78+
/// Ok(())
79+
/// }
80+
/// ```
81+
pub fn new<E>(severity: Severity, error: E) -> Self
82+
where
83+
Box<dyn Error + Sync + Send>: From<E>,
84+
{
85+
Self::from(error).with_severity(severity)
86+
}
87+
88+
/// Creates a new [`BevyError`] with the [`Severity::Ignore`] severity.
89+
///
90+
/// This is a shorthand for <code>[BevyError::new(Severity::Ignore, error)](BevyError::new)</code>.
91+
pub fn ignore<E>(error: E) -> Self
92+
where
93+
Box<dyn Error + Send + Sync>: From<E>,
94+
{
95+
Self::new(Severity::Ignore, error)
96+
}
97+
98+
/// Creates a new [`BevyError`] with the [`Severity::Trace`] severity.
99+
///
100+
/// This is a shorthand for <code>[BevyError::new(Severity::Trace, error)](BevyError::new)</code>.
101+
pub fn trace<E>(error: E) -> Self
102+
where
103+
Box<dyn Error + Send + Sync>: From<E>,
104+
{
105+
Self::new(Severity::Trace, error)
106+
}
107+
108+
/// Creates a new [`BevyError`] with the [`Severity::Debug`] severity.
109+
///
110+
/// This is a shorthand for <code>[BevyError::new(Severity::Debug, error)](BevyError::new)</code>.
111+
pub fn debug<E>(error: E) -> Self
112+
where
113+
Box<dyn Error + Send + Sync>: From<E>,
114+
{
115+
Self::new(Severity::Debug, error)
116+
}
117+
118+
/// Creates a new [`BevyError`] with the [`Severity::Info`] severity.
119+
///
120+
/// This is a shorthand for <code>[BevyError::new(Severity::Info, error)](BevyError::new)</code>.
121+
pub fn info<E>(error: E) -> Self
122+
where
123+
Box<dyn Error + Send + Sync>: From<E>,
124+
{
125+
Self::new(Severity::Info, error)
126+
}
127+
128+
/// Creates a new [`BevyError`] with the [`Severity::Warning`] severity.
129+
///
130+
/// This is a shorthand for <code>[BevyError::new(Severity::Warning, error)](BevyError::new)</code>.
131+
pub fn warning<E>(error: E) -> Self
132+
where
133+
Box<dyn Error + Send + Sync>: From<E>,
134+
{
135+
Self::new(Severity::Warning, error)
136+
}
137+
138+
/// Creates a new [`BevyError`] with the [`Severity::Error`] severity.
139+
///
140+
/// This is a shorthand for <code>[BevyError::new(Severity::Error, error)](BevyError::new)</code>.
141+
pub fn error<E>(error: E) -> Self
142+
where
143+
Box<dyn Error + Send + Sync>: From<E>,
144+
{
145+
Self::new(Severity::Error, error)
146+
}
147+
148+
/// Creates a new [`BevyError`] with the [`Severity::Panic`] severity.
149+
///
150+
/// This is a shorthand for <code>[BevyError::new(Severity::Panic, error)](BevyError::new)</code>.
151+
pub fn panic<E>(error: E) -> Self
152+
where
153+
Box<dyn Error + Send + Sync>: From<E>,
154+
{
155+
Self::new(Severity::Panic, error)
156+
}
157+
158+
/// Checks if the internal error is of the given type.
159+
pub fn is<E: Error + 'static>(&self) -> bool {
160+
self.inner.error.is::<E>()
161+
}
162+
57163
/// Attempts to downcast the internal error to the given type.
58164
pub fn downcast_ref<E: Error + 'static>(&self) -> Option<&E> {
59165
self.inner.error.downcast_ref::<E>()
@@ -65,6 +171,7 @@ impl BevyError {
65171
let f = _f;
66172
let backtrace = &self.inner.backtrace;
67173
if let std::backtrace::BacktraceStatus::Captured = backtrace.status() {
174+
// TODO: Cache
68175
let full_backtrace = std::env::var("BEVY_BACKTRACE").is_ok_and(|val| val == "full");
69176

70177
let backtrace_str = alloc::string::ToString::to_string(backtrace);
@@ -268,6 +375,7 @@ pub fn bevy_error_panic_hook(
268375

269376
#[cfg(test)]
270377
mod tests {
378+
use crate::error::BevyError;
271379

272380
#[test]
273381
#[cfg(not(miri))] // miri backtraces are weird
@@ -278,11 +386,11 @@ mod tests {
278386
Ok(())
279387
}
280388

281-
// SAFETY: this is not safe ... this test could run in parallel with another test
282-
// that writes the environment variable. We either accept that so we can write this test,
283-
// or we don't.
389+
let capture_backtrace = std::env::var_os("RUST_BACKTRACE");
284390

285-
unsafe { std::env::set_var("RUST_BACKTRACE", "1") };
391+
if capture_backtrace.is_none() || capture_backtrace.clone().is_some_and(|s| s == "0") {
392+
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.")
393+
}
286394

287395
let error = i_fail().err().unwrap();
288396
let debug_message = alloc::format!("{error:?}");
@@ -349,4 +457,22 @@ mod tests {
349457
assert_eq!(super::FILTER_MESSAGE, lines.next().unwrap());
350458
assert!(lines.next().is_none());
351459
}
460+
461+
#[test]
462+
fn downcasting() {
463+
#[derive(Debug, PartialEq)]
464+
struct Fun(i32);
465+
466+
impl core::fmt::Display for Fun {
467+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
468+
core::fmt::Debug::fmt(&self, f)
469+
}
470+
}
471+
impl core::error::Error for Fun {}
472+
473+
let new_error = BevyError::new(crate::error::Severity::Debug, Fun(1));
474+
475+
assert!(new_error.is::<Fun>());
476+
assert_eq!(new_error.downcast_ref::<Fun>(), Some(&Fun(1)));
477+
}
352478
}

tools/ci/src/commands/test.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ impl Prepare for TestCommand {
1616
let jobs_ref = &jobs;
1717
let test_threads_ref = &test_threads;
1818

19+
// The bevy_ecs error tests need this set to test backtraces
20+
sh.set_var("RUST_BACKTRACE", "1");
21+
1922
vec![
2023
PreparedCommand::new::<Self>(
2124
cmd!(

0 commit comments

Comments
 (0)