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//!
127//! For example, a validation function with no callee errors returns `Result<_, ValidationError>`,
128//! while a function that wraps I/O errors during parsing could return `Result<_, Exn<ValidationError>>`.
129//! When in doubt, [`Message`] is the default choice.
130//!
131//! ## Translating variants
132//!
133//! The translation depends on the chosen return type. When the function returns a plain error
134//! type like `Result<_, Message>`, return the error directly. When it returns `Result<_, Exn<_>>`,
135//! use [`.raise()`](ErrorExt::raise) to wrap the error into an [`Exn`].
136//!
137//! **Static message variant:**
138//! ```rust,ignore
139//! // BEFORE:
140//! #[error("something went wrong")]
141//! SomethingFailed,
142//! // → Err(Error::SomethingFailed)
143//!
144//! // AFTER (returning Message):
145//! // → Err(message("something went wrong"))
146//!
147//! // AFTER (returning Exn<Message>):
148//! // → Err(message("something went wrong").raise())
149//! ```
150//!
151//! **Formatted message variant:**
152//! ```rust,ignore
153//! // BEFORE:
154//! #[error("unsupported format '{format:?}'")]
155//! Unsupported { format: Format },
156//! // → Err(Error::Unsupported { format })
157//!
158//! // AFTER (returning Message):
159//! // → Err(message!("unsupported format '{format:?}'"))
160//!
161//! // AFTER (returning Exn<Message>):
162//! // → Err(message!("unsupported format '{format:?}'").raise())
163//! ```
164//!
165//! **`#[from]` / `#[error(transparent)]` variant** — delete the variant;
166//! at each call site, use [`ResultExt::or_raise()`] to add context:
167//! ```rust,ignore
168//! // BEFORE:
169//! #[error(transparent)]
170//! Io(#[from] std::io::Error),
171//! // → something_that_returns_io_error()?  // auto-converted via From
172//!
173//! // AFTER (the variant is deleted):
174//! // → something_that_returns_io_error()
175//! //       .or_raise(|| message("context about what failed"))?
176//! ```
177//!
178//! **`#[source]` variant with message** — use [`ResultExt::or_raise()`]:
179//! ```rust,ignore
180//! // BEFORE:
181//! #[error("failed to parse config")]
182//! Config(#[source] config::Error),
183//! // → Err(Error::Config(err))
184//!
185//! // AFTER:
186//! // → config_call().or_raise(|| message("failed to parse config"))?
187//! ```
188//!
189//! **Guard / assertion** — use [`ensure!`]:
190//! ```rust,ignore
191//! // BEFORE:
192//! if !condition {
193//!     return Err(Error::SomethingFailed);
194//! }
195//!
196//! // AFTER (returning ValidationError):
197//! ensure!(condition, ValidationError::new("something went wrong"));
198//!
199//! // AFTER (returning Exn<Message>):
200//! ensure!(condition, message("something went wrong"));
201//! ```
202//!
203//! ## Updating the function signature
204//!
205//! Change the return type, and add the necessary imports:
206//! ```rust,ignore
207//! // BEFORE:
208//! fn parse(input: &str) -> Result<Value, Error> { ... }
209//!
210//! // AFTER (no callee errors wrapped):
211//! fn parse(input: &str) -> Result<Value, Message> { ... }
212//!
213//! // AFTER (callee errors wrapped):
214//! use gix_error::{message, ErrorExt, Exn, Message, ResultExt};
215//! fn parse(input: &str) -> Result<Value, Exn<Message>> { ... }
216//! ```
217//!
218//! ## Updating tests
219//!
220//! Pattern-matching on enum variants can be replaced with string assertions:
221//! ```rust,ignore
222//! // BEFORE:
223//! assert!(matches!(result.unwrap_err(), Error::SomethingFailed));
224//!
225//! // AFTER:
226//! assert_eq!(result.unwrap_err().to_string(), "something went wrong");
227//! ```
228//!
229//! To access error-specific metadata (e.g. the `input` field on [`ValidationError`]),
230//! use [`Exn::downcast_any_ref()`] to find a specific error type within the error tree:
231//! ```rust,ignore
232//! // BEFORE:
233//! match result.unwrap_err() {
234//!     Error::InvalidInput { input } => assert_eq!(input, "bad"),
235//!     other => panic!("unexpected: {other}"),
236//! }
237//!
238//! // AFTER:
239//! let err = result.unwrap_err();
240//! let ve = err.downcast_any_ref::<ValidationError>().expect("is a ValidationError");
241//! assert_eq!(ve.input.as_deref(), Some("bad".into()));
242//! ```
243//!
244//! # Common Pitfalls
245//!
246//! ## Don't use `.erased()` to change the `Exn` type parameter
247//!
248//! [`Exn::raise()`] already nests the current `Exn<E>` as a child of a new `Exn<T>`,
249//! so there is no need to erase the type first. Use [`ErrorExt::and_raise()`] as shorthand:
250//! ```rust,ignore
251//! // WRONG — double-boxes and discards type information:
252//! io_err.raise().erased().raise(message("context"))
253//!
254//! // OK — raise() nests the Exn<io::Error> as a child of Exn<Message> directly:
255//! io_err.raise().raise(message("context"))
256//!
257//! // BEST — and_raise() is a shorthand for .raise().raise():
258//! io_err.and_raise(message("context"))
259//! ```
260//!
261//! Only use [`.erased()`](Exn::erased) when you genuinely need a type-erased `Exn` (no type parameter),
262//! e.g. to return different error types from the same function via `Result<T, Exn>`.
263//!
264//! ## Don't use `.raise_all()` with a single error
265//!
266//! [`Exn::raise_all()`] is meant for creating error trees with *multiple* causes.
267//! If you only have a single causing error, use [`.or_raise()`](ResultExt::or_raise) instead:
268//! ```rust,ignore
269//! // WRONG — raise_all() is for multiple causes, not a single one:
270//! result.map_err(|e| message("context").raise_all(Some(e.raise())))?;
271//!
272//! // RIGHT — or_raise() wraps the error with context directly:
273//! result.or_raise(|| message("context"))?;
274//! ```
275//!
276//! ## Convert `Exn` to [`Error`] at public API boundaries
277//!
278//! Porcelain crates (like `gix`) should not expose [`Exn<Message>`](Exn) in their public API
279//! because it does not implement [`std::error::Error`], which makes it incompatible
280//! with `anyhow`, `Box<dyn Error>`, and the `?` operator in those contexts.
281//!
282//! Instead, convert to [`Error`] (which does implement `std::error::Error`) at the boundary:
283//! ```rust,ignore
284//! // In the porcelain crate's error module:
285//! pub type Error = gix_error::Error;  // not gix_archive::Error (which is Exn<Message>)
286//!
287//! // The conversion happens automatically via From<Exn<E>> for Error,
288//! // so `?` works without explicit .into_error() calls.
289//! ```
290//!
291//! # Feature Flags
292#![cfg_attr(
293    all(doc, feature = "document-features"),
294    doc = ::document_features::document_features!()
295)]
296//! # Why not `anyhow`?
297//!
298//! `anyhow` is a proven and optimized library, and it would certainly suffice for an error-chain based approach
299//! where users are expected to downcast to concrete types.
300//!
301//! What's missing though is `track-caller` which will always capture the location of error instantiation, along with
302//! compatibility for error trees, which are happening when multiple calls are in flight during concurrency.
303//!
304//! Both libraries share the shortcoming of not being able to implement `std::error::Error` on their error type,
305//! and both provide workarounds.
306//!
307//! `exn` is much less optimized, but also costs only a `Box` on the stack,
308//! which in any case is a step up from `thiserror` which exposed a lot of heft to the stack.
309#![deny(missing_docs, unsafe_code)]
310/// A result type to hide the [Exn] error wrapper.
311mod exn;
312
313pub use bstr;
314pub use exn::{ErrorExt, Exn, Frame, OptionExt, ResultExt, Something, Untyped};
315
316/// An error type that wraps an inner type-erased boxed `std::error::Error` or an `Exn` frame.
317///
318/// In that, it's similar to `anyhow`, but with support for tracking the call site and trees of errors.
319///
320/// # Warning: `source()` information is stringified and type-erased
321///
322/// All `source()` values when created with [`Error::from_error()`] are turned into frames,
323/// but lose their type information completely.
324/// This is because they are only seen as reference and thus can't be stored.
325///
326/// # The `auto-chain-error` feature
327///
328/// If it's enabled, this type is merely a wrapper around [`ChainedError`]. This happens automatically
329/// so applications that require this don't have to go through an extra conversion.
330///
331/// When both the `tree-error` and `auto-chain-error` features are enabled, the `tree-error`
332/// behavior takes precedence and this type uses the tree-based representation.
333pub struct Error {
334    #[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
335    inner: error::Inner,
336    #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
337    inner: ChainedError,
338}
339
340/// A Result type that uses the [`Error`] type.
341pub type Result<T = ()> = std::result::Result<T, Error>;
342
343mod error;
344
345/// Various kinds of concrete errors that implement [`std::error::Error`].
346mod concrete;
347pub use concrete::chain::ChainedError;
348pub use concrete::message::{message, Message};
349pub use concrete::validate::ValidationError;
350
351pub(crate) fn write_location(f: &mut std::fmt::Formatter<'_>, location: &std::panic::Location) -> std::fmt::Result {
352    write!(f, ", at {}:{}", location.file(), location.line())
353}