Skip to content

Commit db36799

Browse files
committed
Allow ErrorWithContext to be directly constructed, and unify fail and die formatting
1 parent 37a5fe8 commit db36799

2 files changed

Lines changed: 55 additions & 79 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ is roughly based on [Keep a Changelog], and this project tries to adheres to
1515
- Adds `with_subitem` to add an indented message beneath an error
1616
- Adds `hamming_distance` to the `DistanceFromMatrix` trait for calculating
1717
Hamming distance from an existing substitution matrix
18+
- `ErrorWithContext` can now be constructed directly from a message with a `new` method
1819

1920
### Changed
2021

2122
- Optional auxilary fields in SAM files `SamTags` is now `SamAuxRaw`.
2223
- `SamAuxRaw` can now be parsed into `SamAuxData` which supports all optional field types `A`, `i`, `f`, `Z`, `H`, and `B`.
2324
- `~` is no longer treated as a gap character when assessing partial codons. If `~` is being used to represent gaps, it should be converted to `-` or `.`.
25+
- The style of the backtrace has been improved for the `OrFail` and `Fail` traits
2426

2527
## [0.0.28] - 2026-04-29
2628

src/data/err.rs

Lines changed: 53 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
//!
33
//! This module provides:
44
//!
5-
//! - [`ErrorWithContext`], [`ResultWithErrorContext`], and [`WithErrorContext`]
6-
//! for wrapping errors with additional context while preserving the error
7-
//! source chain.
5+
//! - The error type [`ErrorWithContext`], along with the traits
6+
//! [`ResultWithErrorContext`] and [`WithErrorContext`], for wrapping errors
7+
//! with additional context while preserving the error source chain.
88
//! - [`GetCode`], [`OrFail`], and [`Fail`] for graceful CLI error handling with
99
//! exit codes.
1010
//!
@@ -23,15 +23,19 @@
2323
//! and any runtime penalty is considered negligible compared to the algorithms
2424
//! being run.
2525
//!
26-
//! Internally, this context is added using the [`WithErrorContext`] and
27-
//! [`ResultWithErrorContext`] traits. These traits are made public so that
28-
//! applications can also rely on this machinery. They add context by creating a
26+
//! This context is added using the [`WithErrorContext`] and
27+
//! [`ResultWithErrorContext`] traits. They add context by creating a
2928
//! [`ErrorWithContext`] struct, containing the original error (boxed) as the
3029
//! [`Error::source`] and the context as the new top-level error, stored as a
3130
//! [`String`]. When used in conjuction with an error handling library such as
3231
//! `anyhow` or *Zoe*'s [`OrFail`] or [`Fail`] traits, the stack of errors can
3332
//! be displayed in an application.
3433
//!
34+
//! [`ErrorWithContext`] can also be constructed directly without a source
35+
//! error. In applications that are avoiding dependencies such as `anyhow` and
36+
//! do not want to use [`std::io::Error::other`], [`ErrorWithContext::new`] is a
37+
//! viable option.
38+
//!
3539
//! [`ErrorWithContext`]: crate::data::err::ErrorWithContext
3640
//! [`ResultWithErrorContext`]: crate::data::err::ResultWithErrorContext
3741
//! [`WithErrorContext`]: crate::data::err::WithErrorContext
@@ -198,43 +202,38 @@ where
198202
fn fail(self) -> ! {
199203
if let Ok(bin) = std::env::current_exe() {
200204
eprintln!("Error in {b}", b = bin.display());
201-
eprintln!(
202-
" → {e}",
203-
e = IndentWrapper {
204-
val: &self,
205-
indent: " ",
206-
}
207-
);
208205
} else {
209-
eprintln!("Error: {e}", e = TopLevelErrorDisplay { err: &self });
206+
eprintln!("Error in program");
210207
}
211208

212-
print_backtrace(&self);
209+
print_stack(&self);
213210
std::process::exit(self.get_code());
214211
}
215212

216213
fn die(self, msg: &str) -> ! {
217214
if let Ok(bin) = std::env::current_exe() {
218-
eprintln!("Error in {b}: {msg}\n", b = bin.display());
215+
eprintln!("Error in {b}: {msg}", b = bin.display());
219216
} else {
220-
eprintln!("Error: {msg}\n");
217+
eprintln!("Error: {msg}");
221218
}
222219

223-
eprintln!("{}", TopLevelErrorDisplay { err: &self });
224-
225-
print_backtrace(&self);
220+
print_stack(&self);
226221
std::process::exit(self.get_code());
227222
}
228223
}
229224

230-
/// A wrapper around an error with a new message, and the original error
231-
/// accessible via [`Error::source`].
225+
/// An error type supporting context and a backtrace.
232226
///
233-
/// Additional lines of information can also be added to an [`ErrorWithContext`]
234-
/// using [`with_subitem`].
227+
/// Specifically, this error can hold up to three things:
235228
///
236-
/// When [`unwrap_or_fail`] or [`unwrap_or_die`] is used, this will display the
237-
/// information for the original error as well as any subitems.
229+
/// 1. An optional source error message, which this error wraps. Using
230+
/// [`unwrap_or_fail`] or [`unwrap_or_die`] cause the source error to be
231+
/// shown in the backtrace. This source is accessible via [`Error::source`].
232+
/// 2. A line of context describing the error. This appears as one item in the
233+
/// [`OrFail`] backtrace.
234+
/// 3. Any subitems (additional indented lines with more information that appear
235+
/// below the line of context). This is useful for including the values of
236+
/// variables or other useful information.
238237
///
239238
/// This can be converted to [`std::io::Error`] with [`Into`]. Hence, in
240239
/// functions returning [`std::io::Result`], the `?` operator can be used after
@@ -253,6 +252,23 @@ pub struct ErrorWithContext {
253252
repr: Box<ErrorWithContextRepr>,
254253
}
255254

255+
impl ErrorWithContext {
256+
/// Constructs a new [`ErrorWithContext`] with the given description,
257+
/// without a source error or any subitems.
258+
///
259+
/// The `description` may be anything implementing `Into<String>`. Passing
260+
/// an owned `String` avoids an extra allocation.
261+
pub fn new(description: impl Into<String>) -> Self {
262+
ErrorWithContext {
263+
repr: Box::new(ErrorWithContextRepr {
264+
description: description.into(),
265+
subitem: None,
266+
source: None,
267+
}),
268+
}
269+
}
270+
}
271+
256272
/// The inner representation for an [`ErrorWithContext`]. This is wrapped in a
257273
/// [`Box`] in [`ErrorWithContext`] to reduce the memory of `Result<T,
258274
/// ErrorWithContext>` in the `Ok` case.
@@ -594,52 +610,7 @@ impl<T: Display> Display for IndentWrapper<T> {
594610
}
595611
}
596612

597-
/// A wrapper type altering the implementation of [`Display`], designed for an
598-
/// unindented top-level error in the [`Fail`] backtrace.
599-
///
600-
/// If the top-level error is an [`ErrorWithContext`] (potentially wrapped in
601-
/// some number of [`std::io::Error`]), then the display of the subitems is
602-
/// indented if there are source errors.
603-
struct TopLevelErrorDisplay<'a> {
604-
/// The error of unknown type.
605-
err: &'a (dyn Error + 'static),
606-
}
607-
608-
impl Display for TopLevelErrorDisplay<'_> {
609-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
610-
// Peel away any wrapping std::io::Error
611-
let mut inner = self.err;
612-
while let Some(e) = inner.downcast_ref::<std::io::Error>()
613-
&& let Some(e) = e.get_ref()
614-
{
615-
inner = e;
616-
}
617-
618-
if let Some(e) = inner.downcast_ref::<ErrorWithContext>()
619-
&& e.repr.source.is_some()
620-
{
621-
write!(f, "{}", e.repr.description)?;
622-
623-
if let Some(subitem) = &e.repr.subitem {
624-
write!(
625-
f,
626-
"\n | {}",
627-
IndentWrapper {
628-
val: subitem,
629-
indent: " | ",
630-
}
631-
)?;
632-
}
633-
634-
Ok(())
635-
} else {
636-
// Write the original error, not the one with std::io::Error removed
637-
write!(f, "{}", self.err)
638-
}
639-
}
640-
}
641-
642-
/// Prints the backtrace of an error using [`Error::source`].
613+
/// Prints an error and its backtrace using [`Error::source`].
643614
///
644615
/// This encapsulates the shared logic between [`unwrap_or_die`] and
645616
/// [`unwrap_or_fail`]. Dynamic errors are used to prevent monomorphization on
@@ -648,17 +619,20 @@ impl Display for TopLevelErrorDisplay<'_> {
648619
/// [`unwrap_or_die`]: OrFail::unwrap_or_die
649620
/// [`unwrap_or_fail`]: OrFail::unwrap_or_fail
650621
#[cold]
651-
fn print_backtrace(e: &(dyn Error + 'static)) {
652-
let mut source = e.source();
653-
while let Some(e) = source {
622+
fn print_stack(err: &(dyn Error + 'static)) {
623+
// Wrap the error in Some so that we don't have to write the same logic
624+
// twice
625+
let mut maybe_err = Some(err);
626+
627+
while let Some(err) = maybe_err {
654628
eprintln!(
655-
" → {e}",
656-
e = IndentWrapper {
657-
val: e,
629+
" → {err}",
630+
err = IndentWrapper {
631+
val: err,
658632
indent: " ",
659633
}
660634
);
661635

662-
source = e.source();
636+
maybe_err = err.source();
663637
}
664638
}

0 commit comments

Comments
 (0)