Skip to main content

gix_error/
lib.rs

1//! Common error types and utilities for error handling.
2//!
3//! # Usage
4//!
5//! * When there is **no callee error** to track, use *simple* `std::error::Error` implementations directly,
6//!   e.g. `Result<_, Simple>`.
7//!      - If call-site tracking is important, prefer `Result<_, Exn<Simple>>` instead:
8//!        [`Exn`] stores the location where the error was raised, which plain error values do not.
9//! * When there **is callee error to track** *in a `gix-plumbing`*, use e.g. `Result<_, Exn<Simple>>`.
10//!      - Remember that `Exn<T>` does not implement `std::error::Error` so it's not easy to use outside `gix-` crates.
11//!      - Use the type-erased version in callbacks like [`Exn`] (without type arguments), i.e. `Result<T, Exn>`.
12//! * When there **is callee error to track** *in the `gix` crate*, convert both `std::error::Error` and `Exn<E>` into [`Error`]
13//!
14//! # Standard Error Types
15//!
16//! These should always be used if they match the meaning of the error well enough instead of creating an own
17//! [`Error`](std::error::Error)-implementing type, and used with
18//! [`ResultExt::or_raise(<StandardErrorType>)`](ResultExt::or_raise) or
19//! [`OptionExt::ok_or_raise(<StandardErrorType>)`](OptionExt::ok_or_raise), or sibling methods.
20//!
21//! All these types implement [`Error`](std::error::Error).
22//!
23//! ## [`Message`]
24//!
25//! The baseline that provides a formatted message.
26//! Formatting can more easily be done with the [`message!`] macro as convenience, roughly equivalent to
27//! [`Message::new(format!("…"))`](Message::new) or `format!("…").into()`.
28//!
29//! ## Specialised types
30//!
31//! - [`ValidationError`]
32//!    - like [`Message`], but can optionally store the input that caused the failure.
33//!    - For message-only validation failures, use `Into<ValidationError>` conversions instead of spelling out
34//!      [`ValidationError::new()`]:
35//! ```rust,ignore
36//! // All of these produce ValidationError:
37//! return Err(message("eof reading amount of bits").into());
38//! let value = maybe_value.ok_or(message("missing value").into())?;
39//! let header = maybe_header.ok_or("missing header".into())?;
40//! ```
41//!    - Use [`ValidationError::new_with_input()`] when you need to preserve the offending input.
42//!
43//! # [`Exn<ErrorType>`](Exn) and [`Exn`]
44//!
45//! The [`Exn`] type does not implement [`Error`](std::error::Error) itself, but is able to store causing errors
46//! via [`ResultExt::or_raise()`] (and sibling methods) as well as location information of the creation site.
47//!
48//! While plumbing functions that need to track causes should always return a distinct type like [`Exn<Message>`](Exn),
49//! if that's not possible, use [`Exn::erased`] to let it return `Result<T, Exn>` instead, allowing any return type.
50//!
51//! A side effect of this is that any callee that causes errors needs to be annotated with
52//! `.or_raise(|| message!("context information"))` or `.or_raise_erased(|| message!("context information"))`.
53//!
54//! # Using `Exn` (bare) in closure *bounds*
55//!
56//! Callback and closure **bounds** should use `Result<T, Exn>` (bare, without a type parameter)
57//! rather than `Result<T, Exn<Message>>` or any other specific type. This allows callers to
58//! return any error type from their callbacks without being forced into `Message`.
59//!
60//! Note that functions should still return the most specific type possible (usually `Exn<Message>`);
61//! only the *bound* on the callback parameter should use the bare `Exn`.
62//!
63//! ```rust,ignore
64//! // GOOD — callback bound is flexible, function return is specific:
65//! fn process(cb: impl FnMut() -> Result<(), Exn>) -> Result<(), Exn<Message>> { ... }
66//!
67//! // BAD — forces caller to construct Message errors in their callback:
68//! fn process(cb: impl FnMut() -> Result<(), Exn<Message>>) -> Result<(), Exn<Message>> { ... }
69//! ```
70//!
71//! Inside the function, use [`.or_raise()`](ResultExt::or_raise) to convert the bare `Exn` from the
72//! callback into the function's typed error, adding context:
73//! ```rust,ignore
74//! let entry = callback().or_raise(|| message("context about the callback call"))?;
75//! ```
76//!
77//! Inside a closure that must return bare `Exn`, use [`.or_erased()`](ResultExt::or_erased) to
78//! convert a typed `Exn<E>` to `Exn`, or [`raise_erased()`](ErrorExt::raise_erased) for standalone errors:
79//! ```rust,ignore
80//! |stream| {
81//!     stream.next_entry().or_erased()   // Exn<Message> → Exn
82//! }
83//! ```
84//!
85//! # [`Error`] — `Exn` with `std::error::Error`
86//!
87//! Since [`Exn`] does not implement [`std::error::Error`], it cannot be used where that trait is required
88//! (e.g. `std::io::Error::other()`, or as a `#[source]` in another error type).
89//! The [`Error`] type bridges this gap: it implements [`std::error::Error`] and converts from any
90//! [`Exn<E>`](Exn) via [`From`], preserving the full error tree and location information.
91//!
92//! ```rust,ignore
93//! // Convert an Exn to something usable as std::error::Error:
94//! let exn: Exn<Message> = message("something failed").raise();
95//! let err: gix_error::Error = exn.into();
96//! let err: gix_error::Error = exn.into_error();
97//!
98//! // Useful where std::error::Error is required:
99//! std::io::Error::other(exn.into_error())
100//! ```
101//!
102//! It can also be created directly from any `std::error::Error` via [`Error::from_error()`].
103//!
104//! # Migrating from `thiserror`
105//!
106//! This section describes the mechanical translation from `thiserror` error enums to `gix-error`.
107//! In `Cargo.toml`, replace `thiserror = "<version>"` with `gix-error = { version = "^0.1.0", path = "../gix-error" }`.
108//!
109//! ## Choosing the replacement type
110//!
111//! There are two decisions: whether to wrap in [`Exn`], and which error type to use.
112//!
113//! **With or without [`Exn`]:**
114//!
115//! | `thiserror` enum shape                                      | Wrap in `Exn`? |
116//! |--------------------------------------------------------------|----------------|
117//! | All variants are simple messages (no `#[from]`/`#[source]`)  | No             |
118//! | Has `#[from]` or `#[source]` (wraps callee errors)           | Yes            |
119//!
120//! **Which error type** (used directly or as the `E` in `Exn<E>`):
121//!
122//! | Semantics                                                    | Error type            |
123//! |--------------------------------------------------------------|-----------------------|
124//! | General-purpose error messages                                | [`Message`]           |
125//! | Validation/parsing, optionally storing the offending input   | [`ValidationError`]   |
126//! | Malformed or internally inconsistent data                     | [`CorruptionError`]   |
127//! | A requested resource does not exist                            | [`NotFoundError`]     |
128//!
129//! For example, a validation function with no callee errors returns `Result<_, ValidationError>`,
130//! while a function that wraps I/O errors during parsing could return `Result<_, Exn<ValidationError>>`.
131//! When in doubt, [`Message`] is the default choice.
132//!
133//! ## Translating variants
134//!
135//! The translation depends on the chosen return type. When the function returns a plain error
136//! type like `Result<_, Message>`, return the error directly. When it returns `Result<_, Exn<_>>`,
137//! use [`.raise()`](ErrorExt::raise) to wrap the error into an [`Exn`].
138//!
139//! **Static message variant:**
140//! ```rust,ignore
141//! // BEFORE:
142//! #[error("something went wrong")]
143//! SomethingFailed,
144//! // → Err(Error::SomethingFailed)
145//!
146//! // AFTER (returning Message):
147//! // → Err(message("something went wrong"))
148//!
149//! // AFTER (returning Exn<Message>):
150//! // → Err(message("something went wrong").raise())
151//! ```
152//!
153//! **Formatted message variant:**
154//! ```rust,ignore
155//! // BEFORE:
156//! #[error("unsupported format '{format:?}'")]
157//! Unsupported { format: Format },
158//! // → Err(Error::Unsupported { format })
159//!
160//! // AFTER (returning Message):
161//! // → Err(message!("unsupported format '{format:?}'"))
162//!
163//! // AFTER (returning Exn<Message>):
164//! // → Err(message!("unsupported format '{format:?}'").raise())
165//! ```
166//!
167//! **`#[from]` / `#[error(transparent)]` variant** — delete the variant;
168//! at each call site, use [`ResultExt::or_raise()`] to add context:
169//! ```rust,ignore
170//! // BEFORE:
171//! #[error(transparent)]
172//! Io(#[from] std::io::Error),
173//! // → something_that_returns_io_error()?  // auto-converted via From
174//!
175//! // AFTER (the variant is deleted):
176//! // → something_that_returns_io_error()
177//! //       .or_raise(|| message("context about what failed"))?
178//! ```
179//!
180//! **`#[source]` variant with message** — use [`ResultExt::or_raise()`]:
181//! ```rust,ignore
182//! // BEFORE:
183//! #[error("failed to parse config")]
184//! Config(#[source] config::Error),
185//! // → Err(Error::Config(err))
186//!
187//! // AFTER:
188//! // → config_call().or_raise(|| message("failed to parse config"))?
189//! ```
190//!
191//! **Guard / assertion** — use [`ensure!`]:
192//! ```rust,ignore
193//! // BEFORE:
194//! if !condition {
195//!     return Err(Error::SomethingFailed);
196//! }
197//!
198//! // AFTER (returning ValidationError):
199//! ensure!(condition, ValidationError::new("something went wrong"));
200//!
201//! // AFTER (returning Exn<Message>):
202//! ensure!(condition, message("something went wrong"));
203//! ```
204//!
205//! ## Updating the function signature
206//!
207//! Change the return type, and add the necessary imports:
208//! ```rust,ignore
209//! // BEFORE:
210//! fn parse(input: &str) -> Result<Value, Error> { ... }
211//!
212//! // AFTER (no callee errors wrapped):
213//! fn parse(input: &str) -> Result<Value, Message> { ... }
214//!
215//! // AFTER (callee errors wrapped):
216//! use gix_error::{message, ErrorExt, Exn, Message, ResultExt};
217//! fn parse(input: &str) -> Result<Value, Exn<Message>> { ... }
218//! ```
219//!
220//! ## Updating tests
221//!
222//! Pattern-matching on enum variants can be replaced with string assertions:
223//! ```rust,ignore
224//! // BEFORE:
225//! assert!(matches!(result.unwrap_err(), Error::SomethingFailed));
226//!
227//! // AFTER:
228//! assert_eq!(result.unwrap_err().to_string(), "something went wrong");
229//! ```
230//!
231//! To access error-specific metadata (e.g. the `input` field on [`ValidationError`]),
232//! use [`Exn::downcast_any_ref()`] to find a specific error type within the error tree:
233//! ```rust,ignore
234//! // BEFORE:
235//! match result.unwrap_err() {
236//!     Error::InvalidInput { input } => assert_eq!(input, "bad"),
237//!     other => panic!("unexpected: {other}"),
238//! }
239//!
240//! // AFTER:
241//! let err = result.unwrap_err();
242//! let ve = err.downcast_any_ref::<ValidationError>().expect("is a ValidationError");
243//! assert_eq!(ve.input.as_deref(), Some("bad".into()));
244//! ```
245//!
246//! # Common Pitfalls
247//!
248//! ## Don't use `.erased()` to change the `Exn` type parameter
249//!
250//! [`Exn::raise()`] already nests the current `Exn<E>` as a child of a new `Exn<T>`,
251//! so there is no need to erase the type first. Use [`ErrorExt::and_raise()`] as shorthand:
252//! ```rust,ignore
253//! // WRONG — double-boxes and discards type information:
254//! io_err.raise().erased().raise(message("context"))
255//!
256//! // OK — raise() nests the Exn<io::Error> as a child of Exn<Message> directly:
257//! io_err.raise().raise(message("context"))
258//!
259//! // BEST — and_raise() is a shorthand for .raise().raise():
260//! io_err.and_raise(message("context"))
261//! ```
262//!
263//! Only use [`.erased()`](Exn::erased) when you genuinely need a type-erased `Exn` (no type parameter),
264//! e.g. to return different error types from the same function via `Result<T, Exn>`.
265//!
266//! ## Don't use `.raise_all()` with a single error
267//!
268//! [`Exn::raise_all()`] is meant for creating error trees with *multiple* causes.
269//! If you only have a single causing error, use [`.or_raise()`](ResultExt::or_raise) instead:
270//! ```rust,ignore
271//! // WRONG — raise_all() is for multiple causes, not a single one:
272//! result.map_err(|e| message("context").raise_all(Some(e.raise())))?;
273//!
274//! // RIGHT — or_raise() wraps the error with context directly:
275//! result.or_raise(|| message("context"))?;
276//! ```
277//!
278//! ## Convert `Exn` to [`Error`] at public API boundaries
279//!
280//! Porcelain crates (like `gix`) should **not** expose [`Exn<Message>`](Exn) in their public API
281//! because it does not itself implement [`std::error::Error`].
282//!
283//! Instead, convert to [`Error`] (which does implement `std::error::Error`) at the boundary.
284//! [`Exn`] also converts directly into `Box<dyn std::error::Error + Send + Sync>`, so `?` works
285//! without an explicit conversion when that is the receiving result's error type:
286//! ```rust,ignore
287//! // In the porcelain crate's error module:
288//! pub type Error = gix_error::Error;  // not gix_archive::Error (which is Exn<Message>)
289//!
290//! // The conversion happens automatically via From<Exn<E>> for Error,
291//! // so `?` works without explicit .into_error() calls.
292//! ```
293//!
294//! # Feature Flags
295#![cfg_attr(
296    all(doc, feature = "document-features"),
297    doc = ::document_features::document_features!()
298)]
299//! # Why not `anyhow`?
300//!
301//! `anyhow` is a proven and optimized library, and it would certainly suffice for an error-chain based approach
302//! where users are expected to downcast to concrete types.
303//!
304//! What's missing though is `track-caller` which will always capture the location of error instantiation, along with
305//! compatibility for error trees, which are happening when multiple calls are in flight during concurrency.
306//!
307//! Both libraries share the shortcoming of not being able to implement `std::error::Error` on their error type,
308//! and both provide workarounds.
309//!
310//! `exn` is much less optimized, but also costs only a `Box` on the stack,
311//! which in any case is a step up from `thiserror` which exposed a lot of heft to the stack.
312#![deny(missing_docs, unsafe_code)]
313/// A result type to hide the [Exn] error wrapper.
314mod exn;
315
316pub use bstr;
317pub use exn::{BoxedResultExt, ErrorExt, Exn, Frame, OptionExt, ResultExt, Something, Untyped};
318
319/// An error type that wraps an inner type-erased boxed `std::error::Error` or an `Exn` frame.
320///
321/// In that, it's similar to `anyhow`, but with support for tracking the call site and trees of errors.
322///
323/// # Warning: `source()` information is stringified and type-erased
324///
325/// All `source()` values when created with [`Error::from_error()`] are turned into frames,
326/// but lose their type information completely. An existing `Error` is retained as a nested error instead.
327/// This is because they are only seen as reference and thus can't be stored.
328///
329/// # The `auto-chain-error` feature
330///
331/// If it's enabled, this type is merely a wrapper around [`ChainedError`]. This happens automatically
332/// so applications that require this don't have to go through an extra conversion.
333///
334/// When both the `tree-error` and `auto-chain-error` features are enabled, the `tree-error`
335/// behavior takes precedence and this type uses the tree-based representation.
336pub struct Error {
337    #[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
338    inner: error::Inner,
339    #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
340    inner: ChainedError,
341}
342
343/// A Result type that uses the [`Error`] type.
344pub type Result<T = ()> = std::result::Result<T, Error>;
345
346mod error;
347pub use error::{DisplaySource, can_retry};
348
349/// Various kinds of concrete errors that implement [`std::error::Error`].
350mod concrete;
351pub use concrete::chain::ChainedError;
352pub use concrete::classify::{CorruptionError, NotFoundError, RetryableError};
353pub use concrete::message::{Message, message};
354pub use concrete::validate::ValidationError;
355
356pub(crate) fn write_location(f: &mut std::fmt::Formatter<'_>, location: &std::panic::Location) -> std::fmt::Result {
357    write!(f, ", at {}:{}", location.file(), location.line())
358}