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//!
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