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