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 `ExnResult<_, 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. `ExnResult<_, 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. `ExnResult<T>`.
12//! * When there **is callee error to track** *in the `gix` crate*, convert both `std::error::Error` and `Exn<E>` into [`Error`]
13//!
14//! [`ExnResult<T, E>`](ExnResult) abbreviates a result with an [`Exn<E>`](Exn) error. Its defaults are
15//! `T = ()` and `E = exn::Untyped`, matching bare [`Exn`]. Use `ExnMessageResult<T>` for message contexts.
16//!
17//! # Standard Error Types
18//!
19//! These should always be used if they match the meaning of the error well enough instead of creating an own
20//! [`Error`](std::error::Error)-implementing type, and used with
21//! [`ResultExt::or_raise(<StandardErrorType>)`](ResultExt::or_raise) or
22//! [`OptionExt::ok_or_raise(<StandardErrorType>)`](OptionExt::ok_or_raise), or sibling methods.
23//!
24//! All these types implement [`Error`](std::error::Error).
25//!
26//! ## [`Message`] and [`ClassificationMarker`]
27//!
28//! [`Message`] combines a diagnostic message, an optional [`Class`], and named scalar values. Use it
29//! instead of a chain of type-bearing errors when those layers only provide the category and details of a single
30//! failure. [`not_found()`], [`validation()`], [`corruption()`], [`retryable()`], [`resource_exhaustion()`],
31//! [`allocation_limit()`], [`allocation_failure()`], and [`io()`] construct classified messages.
32//! [`message()`] and [`Message::new()`] start without a class or values. [`Message::with_class()`] and
33//! [`Message::with()`] add them to the same diagnostic. Use [`message!`] for formatting, equivalent to
34//! [`Message::new(format!("…"))`](Message::new) or `format!("…").into()`.
35//!
36//! Classification does not determine which diagnostic values can be attached. For example,
37//! `corruption("Malformed reference").with("input", bytes)` preserves offending bytes in the same
38//! error that describes their corruption. No extra validation error is needed just to store input.
39//! Use explicit classified constructors: converting a string to [`Message`] does not infer a class
40//! from the function's return type.
41//!
42//! | Type | Diagnostic | Classification | Purpose |
43//! |------|------------|----------------|---------|
44//! | [`Message`] | Visible message and optional values | Optional | Describe a failure without a custom error type |
45//! | [`ClassificationMarker`] | Transparent, no diagnostic of its own | Required | Classify an existing error while preserving its concrete type |
46//!
47//! ```
48//! use gix_error::{ErrorExt, Message, MetadataValue};
49//!
50//! let error = gix_error::not_found("Reference does not exist")
51//!     .with("path", std::path::Path::new("HEAD"))
52//!     .raise();
53//! assert!(error.is_not_found());
54//! assert!(error.probable_cause().is::<Message>());
55//! assert_eq!(error.metadata().next().expect("lookup details")["path"], MetadataValue::Path("HEAD".into()));
56//! ```
57//!
58//! Callers should add context using information they already possess and document its keys on the function
59//! that returns it. Preserve real callee errors, especially concrete recovery signals and complex results
60//! discovered by the callee, such as partial outcomes:
61//!
62//! ```
63//! use gix_error::{message, ResultExt, MetadataValue};
64//!
65//! let error = Err::<(), _>(std::io::Error::from(std::io::ErrorKind::NotFound))
66//!     .or_raise(|| message("Could not read reference").with("path", std::path::Path::new("HEAD")))
67//!     .expect_err("the lookup failed");
68//! assert!(error.is_not_found());
69//! assert!(error.probable_cause().is::<std::io::Error>());
70//! assert_eq!(error.error().class, None, "the callee, not the context, supplies the classification");
71//! let values = error.metadata().next().expect("lookup context");
72//! assert_eq!(values["path"], MetadataValue::Path("HEAD".into()));
73//! ```
74//!
75//! [`Exn::metadata()`] and [`Error::metadata()`] yield each message's non-empty [`Metadata`] dictionary in error traversal order.
76//! Each dictionary maps names to [`MetadataValue`]s. Keys are local to their context; dictionaries from independent causes
77//! are never combined. To identify a specific failure without inspecting its values, see
78//! [matching a specific failure](#matching-a-specific-failure).
79//!
80//! # [`Exn<ErrorType>`](Exn) and [`Exn`]
81//!
82//! The [`Exn`] type does not implement [`Error`](std::error::Error) itself, but is able to store causing errors
83//! via [`ResultExt::or_raise()`] (and sibling methods) as well as location information of the creation site.
84//!
85//! While plumbing functions that need to track causes should always return a distinct type like [`Exn<Message>`](Exn),
86//! if that's not possible, use [`Exn::erased`] to let it return `ExnResult<T>` instead, allowing any return type.
87//!
88//! A side effect of this is that any callee that causes errors needs to be annotated with
89//! `.or_raise(|| message!("context information"))` or `.or_raise_erased(|| message!("context information"))`.
90//!
91//! # Using [`ExnResult`] in closure *bounds*
92//!
93//! Callback and closure **bounds** should use `ExnResult<T>` (without an explicit error type)
94//! rather than `ExnMessageResult<T>` or any other specific type. This allows callers to
95//! return any error type from their callbacks without being forced into `Message`.
96//!
97//! Functions should still return the most specific type possible (usually `ExnMessageResult<T>`);
98//! only the *bound* on the callback parameter should use the default, erased error type.
99//!
100//! ```rust,ignore
101//! use gix_error::{ExnMessageResult, ExnResult};
102//!
103//! // GOOD — callback bound is flexible, function return is specific:
104//! fn process(cb: impl FnMut() -> ExnResult) -> ExnMessageResult { ... }
105//!
106//! // BAD — forces caller to construct Message errors in their callback:
107//! fn process(cb: impl FnMut() -> ExnMessageResult) -> ExnMessageResult { ... }
108//! ```
109//!
110//! Inside the function, use [`.or_raise()`](ResultExt::or_raise) to convert the bare `Exn` from the
111//! callback into the function's typed error, adding context:
112//! ```rust,ignore
113//! let entry = callback().or_raise(|| message("context about the callback call"))?;
114//! ```
115//!
116//! Inside a closure that must return `ExnResult<T>`, use [`.or_erased()`](ResultExt::or_erased) to
117//! convert a typed `Exn<E>` to `Exn`, or [`raise_erased()`](ErrorExt::raise_erased) for standalone errors:
118//! ```rust,ignore
119//! |stream| {
120//!     stream.next_entry().or_erased()   // Exn<Message> → Exn
121//! }
122//! ```
123//!
124//! # [`Error`] — `Exn` with `std::error::Error`
125//!
126//! Since [`Exn`] does not implement [`std::error::Error`], it cannot be used where that trait is required
127//! (e.g. `std::io::Error::other()`, or as a `#[source]` in another error type).
128//! The [`Error`] type bridges this gap: it implements [`std::error::Error`] and converts from any
129//! [`Exn<E>`](Exn) via [`From`], preserving the full error tree and location information.
130//!
131//! ```rust,ignore
132//! // Convert an Exn to something usable as std::error::Error:
133//! let exn: Exn<Message> = message("something failed").raise();
134//! let err: gix_error::Error = exn.into();
135//! let err: gix_error::Error = exn.into_error();
136//!
137//! // Useful where std::error::Error is required:
138//! std::io::Error::other(exn.into_error())
139//! ```
140//!
141//! It can also be created directly from any `std::error::Error` via [`Error::from_error()`].
142//!
143//! # Tests with [`TestResult`]
144//!
145//! Return [`TestResult`] from `#[test]` functions to propagate ordinary errors, [`Exn<E>`](Exn), and [`Error`]
146//! directly with `?`. It defaults to `Result<(), TestError>`; helpers returning a value can use `TestResult<T>`.
147//! Accepted errors must convert into `Box<dyn std::error::Error + Send + Sync + 'static>`.
148//!
149//! When a test returns an error, Rust's test harness prints [`TestError`]'s [`Debug`](std::fmt::Debug) output,
150//! including the complete diagnostic tree or chain and captured caller locations.
151//!
152//! ```rust,test_harness
153//! use gix_error::{message, ResultExt, TestResult};
154//!
155//! #[test]
156//! fn parses_count() -> TestResult {
157//!     let expected: usize = "42".parse()?;
158//!     let actual = "42".parse::<usize>().or_raise(|| message("could not parse count"))?;
159//!     assert_eq!(actual, expected, "context preserves the parsed count");
160//!     Ok(())
161//! }
162//! ```
163//!
164//! # Migrating from `thiserror`
165//!
166//! This section describes the mechanical translation from `thiserror` error enums to `gix-error`.
167//! In `Cargo.toml`, replace `thiserror = "<version>"` with `gix-error = { version = "^0.1.0", path = "../gix-error" }`.
168//!
169//! ## Choosing the replacement type
170//!
171//! Use [`ExnMessageResult`] for diagnostic messages, including validation failures without callee errors.
172//! [`Message`] carries an optional class and named scalar values; [`Exn`] retains the diagnostic context and causes.
173//! Keep a concrete error type in [`ExnResult`] when recovery requires its specific payload.
174//! Use [`Result`] at porcelain boundaries that return [`Error`].
175//!
176//! Use the chosen type directly in signatures, importing it under its canonical name where helpful.
177//! Crate-specific and operation-specific forwarding aliases or renamed error exports are unnecessary.
178//! Facades may re-export the canonical types, as `gix` does with `Error`, `Exn`, `Result`, `ExnResult`, and `ExnMessageResult`.
179//! Always import the result aliases directly and use their bare names in signatures.
180//!
181//! ## Translating variants
182//!
183//! Use [`.raise()`](ErrorExt::raise) to wrap standalone errors into an [`Exn`], and
184//! [`ResultExt::or_raise()`] to preserve callee errors with additional context.
185//!
186//! **Static message variant:**
187//! ```rust,ignore
188//! // BEFORE:
189//! #[error("something went wrong")]
190//! SomethingFailed,
191//! // → Err(Error::SomethingFailed)
192//!
193//! // AFTER (returning Exn<Message>):
194//! // → Err(message("something went wrong").raise())
195//! ```
196//!
197//! **Formatted message variant:**
198//! ```rust,ignore
199//! // BEFORE:
200//! #[error("unsupported format '{format:?}'")]
201//! Unsupported { format: Format },
202//! // → Err(Error::Unsupported { format })
203//!
204//! // AFTER (returning Exn<Message>):
205//! // → Err(message!("unsupported format '{format:?}'").raise())
206//! ```
207//!
208//! **`#[from]` / `#[error(transparent)]` variant** — delete the variant;
209//! at each call site, use [`ResultExt::or_raise()`] to add context:
210//! ```rust,ignore
211//! // BEFORE:
212//! #[error(transparent)]
213//! Io(#[from] std::io::Error),
214//! // → something_that_returns_io_error()?  // auto-converted via From
215//!
216//! // AFTER (the variant is deleted):
217//! // → something_that_returns_io_error()
218//! //       .or_raise(|| message("context about what failed"))?
219//! ```
220//!
221//! **`#[source]` variant with message** — use [`ResultExt::or_raise()`]:
222//! ```rust,ignore
223//! // BEFORE:
224//! #[error("failed to parse config")]
225//! Config(#[source] config::Error),
226//! // → Err(Error::Config(err))
227//!
228//! // AFTER:
229//! // → config_call().or_raise(|| message("failed to parse config"))?
230//! ```
231//!
232//! **Guard / assertion** — use [`ensure!`]:
233//! ```rust,ignore
234//! // BEFORE:
235//! if !condition {
236//!     return Err(Error::SomethingFailed);
237//! }
238//!
239//! // AFTER (returning Exn<Message>, with a validation class):
240//! ensure!(condition, gix_error::validation("something went wrong"));
241//!
242//! // AFTER (returning Exn<Message>):
243//! ensure!(condition, message("something went wrong"));
244//! ```
245//!
246//! ## Updating the function signature
247//!
248//! Change the return type, and add the necessary imports:
249//! ```rust,ignore
250//! // BEFORE:
251//! fn parse(input: &str) -> Result<Value, Error> { ... }
252//!
253//! // AFTER:
254//! use gix_error::{message, ErrorExt, ExnMessageResult, ResultExt};
255//! fn parse(input: &str) -> ExnMessageResult<Value> { ... }
256//! ```
257//!
258//! ## Updating tests
259//!
260//! Tests of diagnostic wording can use string assertions:
261//! ```rust,ignore
262//! // BEFORE:
263//! assert!(matches!(result.unwrap_err(), Error::SomethingFailed));
264//!
265//! // AFTER:
266//! assert_eq!(result.unwrap_err().to_string(), "something went wrong");
267//! ```
268//!
269//! For semantic checks, both [`Exn`] and [`Error`] provide [`is_retryable()`](Exn::is_retryable),
270//! [`is_not_found()`](Exn::is_not_found), [`is_validation()`](Exn::is_validation),
271//! [`is_corrupted()`](Exn::is_corrupted), and [`is_resource_exhausted()`](Exn::is_resource_exhausted).
272//! These inspect causes as well as the outermost error. `is_retryable()` requires an explicit retry classification;
273//! [`Exn::can_retry()`] and [`Error::can_retry()`] additionally recognize certain I/O error kinds.
274//! Use [`Exn::probable_cause()`] to inspect the likely root cause. It follows a single causal path, stopping at the
275//! first branch rather than choosing an arbitrary sibling. Classification markers are transparent to this selection.
276//! [`Exn::classify()`] and [`Error::classify()`] expose each known classification together with its original error.
277//! Custom payloads of [`std::io::Error`] are inspected too, including any nested [`Error`] trees.
278//!
279//! [`Message`] supplies its own diagnostic and optional classification. In contrast, [`ClassificationMarker`]
280//! only supplies classification metadata. Use [`ClassificationMarker::with_source()`] to classify an existing error
281//! while preserving its concrete type:
282//! ```
283//! use gix_error::{Class, ClassificationMarker, ErrorExt};
284//!
285//! let err = ClassificationMarker::with_source(
286//!     Class::Retryable,
287//!     std::io::Error::from(std::io::ErrorKind::AlreadyExists),
288//! ).raise();
289//! assert!(err.is_retryable());
290//! assert!(err.probable_cause().is::<std::io::Error>());
291//! assert!(err.downcast_any_ref::<ClassificationMarker>().is_none());
292//! ```
293//!
294//! Custom error types preserve classifications by exposing their immediate cause as `Some(inner)` from
295//! [`std::error::Error::source()`]. Forwarding to `inner.source()` instead can hide a classification carried by
296//! `inner` itself. A custom leaf error can borrow a constant such as [`ClassificationMarker::NOT_FOUND`]
297//! as its source to preserve its classification without defining a static or adding a generic category to its diagnostic:
298//! ```
299//! use gix_error::{ClassificationMarker, ErrorExt};
300//!
301//! #[derive(Debug)]
302//! struct MissingObject;
303//!
304//! impl std::fmt::Display for MissingObject {
305//!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306//!         f.write_str("the requested object is missing from the object database")
307//!     }
308//! }
309//!
310//! impl std::error::Error for MissingObject {
311//!     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
312//!         Some(const { &ClassificationMarker::NOT_FOUND })
313//!     }
314//! }
315//!
316//! let err = MissingObject.raise();
317//! assert!(err.is_not_found());
318//! assert!(err.probable_cause().is::<MissingObject>());
319//! ```
320//! Use classification predicates rather than downcasting to [`Message`] just to recognize
321//! a category: diagnostic iterators and downcasts skip all classification markers. Exception and test reports
322//! omit their wrappers too, while raw [`std::error::Error::source()`] chains retain them. Genuine classified errors
323//! remain causal and can still be downcast to inspect their payloads. When storing an [`Exn`] in a custom error, convert it with
324//! [`Exn::into_error()`] so the source can expose its complete tree.
325//!
326//! To access scalar diagnostics such as offending input, inspect the documented [metadata](Exn::metadata()) key:
327//! ```
328//! use gix_error::{ErrorExt, MetadataValue};
329//!
330//! let err = gix_error::validation("invalid input").with("input", b"bad".as_slice()).raise();
331//! let values = err.metadata().find(|values| values.contains_key("input")).expect("input context");
332//! assert_eq!(values["input"], MetadataValue::Bytes("bad".into()));
333//! ```
334//!
335//! ## Matching a specific failure
336//!
337//! Use [`Class::Tagged`] when a broad category such as [`Class::NotFound`] isn't specific enough for recovery.
338//! A single stable, namespaced tag identifies the condition without a custom error type or metadata matching.
339//! Functions returning tagged errors document their tags as part of their recovery contract, independently of
340//! diagnostic wording. A tag implies no other classification. When a general class also applies, chain a
341//! [`ClassificationMarker`] to retain it without adding a visible diagnostic.
342//!
343//! ```
344//! use gix_error::{Class, ClassificationMarker, ErrorExt, message};
345//!
346//! let missing_binary_result = Class::Tagged("gix_merge::tree::missing_binary_merge_result");
347//! let err = message("The binary merge result could not be selected")
348//!     .with_class(missing_binary_result)
349//!     .raise()
350//!     .chain(ClassificationMarker::NOT_FOUND)
351//!     .raise(message("Tree merge failed"));
352//!
353//! assert!(err.classify().has(missing_binary_result));
354//! assert!(err.is_not_found());
355//! ```
356//!
357//! [`types::Classifications::has()`] also finds tagged causes through wrapping contexts and [`Error`] conversion.
358//! Matching one cause does not make other failures in an aggregate ignorable.
359//!
360//! # Common Pitfalls
361//!
362//! ## Don't use `.erased()` to change the `Exn` type parameter
363//!
364//! [`Exn::raise()`] already nests the current `Exn<E>` as a child of a new `Exn<T>`,
365//! so there is no need to erase the type first. Use [`ErrorExt::and_raise()`] as shorthand:
366//! ```rust,ignore
367//! // WRONG — double-boxes and discards type information:
368//! io_err.raise().erased().raise(message("context"))
369//!
370//! // OK — raise() nests the Exn<io::Error> as a child of Exn<Message> directly:
371//! io_err.raise().raise(message("context"))
372//!
373//! // BEST — and_raise() is a shorthand for .raise().raise():
374//! io_err.and_raise(message("context"))
375//! ```
376//!
377//! Only use [`.erased()`](Exn::erased) when you genuinely need a type-erased `Exn` (no type parameter),
378//! e.g. to return different error types from the same function via `ExnResult<T>`.
379//!
380//! ## Don't use `.raise_all()` with a single error
381//!
382//! [`Exn::raise_all()`] is meant for creating error trees with *multiple* causes.
383//! If you only have a single causing error, use [`.or_raise()`](ResultExt::or_raise) instead:
384//! ```rust,ignore
385//! // WRONG — raise_all() is for multiple causes, not a single one:
386//! result.map_err(|e| message("context").raise_all(Some(e.raise())))?;
387//!
388//! // RIGHT — or_raise() wraps the error with context directly:
389//! result.or_raise(|| message("context"))?;
390//! ```
391//!
392//! ## Convert `Exn` to [`Error`] at public API boundaries
393//!
394//! Porcelain crates (like `gix`) should **not** expose [`Exn<Message>`](Exn) in their public API
395//! because it does not itself implement [`std::error::Error`].
396//!
397//! Instead, convert to [`Error`] (which does implement `std::error::Error`) at the boundary.
398//! [`Exn`] also converts directly into `Box<dyn std::error::Error + Send + Sync>`, so `?` works
399//! without an explicit conversion when that is the receiving result's error type:
400//! ```rust,ignore
401//! fn porcelain_operation() -> Result<(), gix_error::Error> {
402//!     // From<Exn<E>> for Error converts the plumbing error at this boundary.
403//!     plumbing_operation()?;
404//!     Ok(())
405//! }
406//! ```
407//!
408//! # Supporting types
409//!
410//! Frequently used error types, extension traits, result aliases, and constructors are available at the crate root.
411//! Utility types for flattened chains, classification, and diagnostic display live in [`types`]. Exception frames
412//! and the default type-erasure marker live in [`exn`]; [`Exn`] and its extension traits are only exported at the root.
413//!
414//! # Feature Flags
415#![cfg_attr(
416    all(doc, feature = "document-features"),
417    doc = ::document_features::document_features!()
418)]
419//! # Why not `anyhow`?
420//!
421//! `anyhow` is a proven and optimized library, and it would certainly suffice for an error-chain based approach
422//! where users are expected to downcast to concrete types.
423//!
424//! What's missing though is `track-caller` which will always capture the location of error instantiation, along with
425//! compatibility for error trees, which are happening when multiple calls are in flight during concurrency.
426//!
427//! Both libraries share the shortcoming of not being able to implement `std::error::Error` on their error type,
428//! and both provide workarounds.
429//!
430//! `exn` is much less optimized, but also costs only a `Box` on the stack,
431//! which in any case is a step up from `thiserror` which exposed a lot of heft to the stack.
432#![deny(missing_docs, unsafe_code)]
433pub mod exn;
434pub mod types;
435
436pub use bstr;
437pub use exn::{
438    ext::{BoxedResultExt, ErrorExt, OptionExt, ResultExt},
439    impls::Exn,
440};
441
442/// An error type that wraps an inner type-erased boxed `std::error::Error` or an `Exn` frame.
443///
444/// In that, it's similar to `anyhow`, but with support for tracking the call site and trees of errors.
445///
446/// # Native error sources
447///
448/// [`Error::from_error()`] retains the concrete error and its native [`source()`](std::error::Error::source) chain.
449/// Use [`Error::downcast_any_ref()`] or [`Error::iter_errors()`] to inspect the original types, including sources
450/// within nested [`Error`] values. This also applies when the `auto-chain-error` feature is enabled.
451///
452/// # The `auto-chain-error` feature
453///
454/// If it's enabled, this type is merely a wrapper around [`ChainedError`](types::ChainedError). This happens automatically
455/// so applications that require this don't have to go through an extra conversion.
456///
457/// When both the `tree-error` and `auto-chain-error` features are enabled, the `tree-error`
458/// behavior takes precedence and this type uses the tree-based representation.
459pub struct Error {
460    #[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
461    inner: error::Inner,
462    #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
463    inner: types::ChainedError,
464}
465
466fn root_error_eq(mut error: &(dyn std::error::Error + 'static), other: &str) -> bool {
467    while let Some(nested) = error.downcast_ref::<Error>() {
468        error = nested.error();
469    }
470    error.to_string() == other
471}
472
473impl PartialEq<str> for Error {
474    fn eq(&self, other: &str) -> bool {
475        root_error_eq(self.error(), other)
476    }
477}
478
479impl PartialEq<&str> for Error {
480    fn eq(&self, other: &&str) -> bool {
481        <Self as PartialEq<str>>::eq(self, other)
482    }
483}
484
485impl PartialEq<String> for Error {
486    fn eq(&self, other: &String) -> bool {
487        <Self as PartialEq<str>>::eq(self, other)
488    }
489}
490
491/// A Result type that uses the [`Error`] type.
492pub type Result<T = ()> = std::result::Result<T, Error>;
493
494/// A result with an [`Exn<E>`](Exn) error, defaulting to unit success and an erased error type.
495///
496/// `ExnResult<T>` uses the same [`exn::Untyped`] marker as bare [`Exn`]. Specify `E` to retain a
497/// concrete error type; [`ExnMessageResult`] is the shorthand for message contexts. All standard result operations and
498/// [`ResultExt`] methods remain available. Use [`ResultExt::or_erased()`] for callbacks accepting
499/// different error types, and `?` to propagate exceptions into [`Error`] at API boundaries.
500///
501/// ```
502/// use gix_error::{message, ErrorExt, ExnMessageResult, ExnResult, ResultExt};
503///
504/// fn parse_count(input: &str) -> ExnMessageResult<u64> {
505///     input.parse::<u64>().or_raise(|| message("could not parse count"))
506/// }
507///
508/// fn process(callback: impl FnOnce() -> ExnResult<u64>) -> ExnMessageResult {
509///     let count = callback().or_raise(|| message("callback failed"))?;
510///     assert_eq!(count, 42, "the callback supplies the parsed count");
511///     Ok(())
512/// }
513///
514/// let done: ExnResult = process(|| parse_count("42").or_erased()).or_erased();
515/// done?;
516///
517/// let io: ExnResult<(), std::io::Error> =
518///     Err(std::io::Error::from(std::io::ErrorKind::NotFound).raise());
519/// assert_eq!(io.expect_err("the I/O operation failed").error().kind(), std::io::ErrorKind::NotFound);
520/// # Ok::<(), gix_error::Error>(())
521/// ```
522pub type ExnResult<T = (), E = exn::Untyped> = std::result::Result<T, Exn<E>>;
523
524/// A result with a [`Message`] exception, defaulting to unit success.
525///
526/// This is [`ExnResult<T, Message>`](ExnResult). Use it for operations that attach message contexts
527/// with [`ResultExt::or_raise()`], or return standalone messages with [`ErrorExt::raise()`].
528/// Use [`ExnResult<T>`](ExnResult) with its erased error type for callback bounds.
529///
530/// ```
531/// use gix_error::{message, ErrorExt, ExnMessageResult};
532///
533/// fn validate(ready: bool) -> ExnMessageResult {
534///     if !ready {
535///         return Err(message("not ready").raise());
536///     }
537///     Ok(())
538/// }
539///
540/// validate(true)?;
541/// assert_eq!(validate(false).expect_err("not ready").error().message, "not ready");
542/// # Ok::<(), gix_error::Error>(())
543/// ```
544pub type ExnMessageResult<T = ()> = ExnResult<T, Message>;
545
546mod test;
547pub use test::{TestError, TestResult};
548
549mod error;
550pub use error::{Class, classify};
551
552/// Various kinds of concrete errors that implement [`std::error::Error`].
553mod concrete;
554
555pub use concrete::classify::{ClassificationMarker, ResourceExhaustionKind};
556pub use concrete::message::message;
557pub use concrete::metadata::{
558    Message, Metadata, MetadataValue, allocation_failure, allocation_limit, corruption, io, not_found,
559    resource_exhaustion, retryable, validation,
560};
561
562pub(crate) fn write_location(f: &mut std::fmt::Formatter<'_>, location: &std::panic::Location) -> std::fmt::Result {
563    write!(f, ", at {}:{}", location.file(), location.line())
564}