error_rail/context/
mod.rs

1//! Helpers for attaching rich context metadata to errors.
2//!
3//! Key features:
4//! - [`with_context`] / [`with_context_result`] wrap any `Result` while preserving
5//!   structured [`ErrorContext`] values.
6//! - [`ErrorPipeline`] enables chaining operations, collecting pending contexts,
7//!   and mapping/recovering errors before finalizing into a [`ComposableError`].
8//! - Convenience builders such as [`context_fn`], [`accumulate_context`], and
9//!   [`context_accumulator`] let you compose context-aware closures.
10//!
11//! See the crate-level docs for a high-level overview of when to prefer these
12//! utilities over bare `Result` transformations.
13
14use crate::types::alloc_type::{Box, String};
15use crate::types::composable_error::ComposableError;
16use crate::types::BoxedComposableResult;
17use crate::types::{ErrorContext, ErrorPipeline};
18use crate::{traits::IntoErrorContext, ErrorVec};
19use core::fmt::Display;
20
21/// Wraps an error with a single context entry.
22///
23/// Creates a new [`ComposableError`] containing the given error and context.
24///
25/// # Arguments
26///
27/// * `error` - The core error to wrap
28/// * `context` - Context information to attach
29///
30/// # Examples
31///
32/// ```
33/// use error_rail::{with_context, ErrorContext};
34///
35/// let err = with_context("io failed", ErrorContext::tag("disk"));
36/// assert_eq!(err.context().len(), 1);
37/// ```
38#[inline]
39pub fn with_context<E, C>(error: E, context: C) -> ComposableError<E>
40where
41    C: IntoErrorContext,
42{
43    ComposableError::new(error).with_context(context)
44}
45
46/// Transforms a `Result` by adding context to any error.
47///
48/// On `Err`, wraps the error in a boxed [`ComposableError`] with the provided context.
49/// On `Ok`, returns the success value unchanged.
50///
51/// # Arguments
52///
53/// * `result` - The result to transform
54/// * `context` - Context to attach on error
55///
56/// # Examples
57///
58/// ```
59/// use error_rail::{with_context_result, ErrorContext};
60///
61/// let result: Result<i32, &str> = Err("failed");
62/// let enriched = with_context_result(result, ErrorContext::tag("auth"));
63/// assert!(enriched.is_err());
64/// ```
65#[inline]
66pub fn with_context_result<T, E, C>(result: Result<T, E>, context: C) -> BoxedComposableResult<T, E>
67where
68    C: IntoErrorContext,
69{
70    result.map_err(|e| Box::new(with_context(e, context)))
71}
72
73/// Creates a reusable closure that wraps errors with a fixed context.
74///
75/// Returns a function that can be used with `map_err` to attach the same
76/// context to multiple error paths.
77///
78/// # Arguments
79///
80/// * `context` - The context to attach (must be `Clone`)
81///
82/// # Examples
83///
84/// ```
85/// use error_rail::{context_fn, ErrorContext};
86///
87/// let add_tag = context_fn(ErrorContext::tag("network"));
88/// let err = add_tag("timeout");
89/// assert_eq!(err.context().len(), 1);
90/// ```
91#[inline]
92pub fn context_fn<E, C>(context: C) -> impl Fn(E) -> ComposableError<E>
93where
94    C: IntoErrorContext + Clone,
95{
96    move |error| with_context(error, context.clone())
97}
98
99/// Creates an [`ErrorPipeline`] from a result.
100///
101/// Convenience function equivalent to `ErrorPipeline::new(result)`.
102///
103/// # Arguments
104///
105/// * `result` - The result to wrap
106///
107/// # Examples
108///
109/// ```
110/// use error_rail::{error_pipeline, context};
111///
112/// let pipeline = error_pipeline::<u32, &str>(Err("failed"))
113///     .with_context(context!("step failed"));
114/// ```
115#[inline]
116pub fn error_pipeline<T, E>(result: Result<T, E>) -> ErrorPipeline<T, E> {
117    ErrorPipeline::new(result)
118}
119
120/// Wraps an error with multiple context entries at once.
121///
122/// Creates a [`ComposableError`] and attaches all provided contexts.
123///
124/// # Arguments
125///
126/// * `error` - The core error to wrap
127/// * `contexts` - Iterator of contexts to attach
128///
129/// # Examples
130///
131/// ```
132/// use error_rail::{accumulate_context, ErrorContext};
133///
134/// let contexts = vec![ErrorContext::tag("auth"), ErrorContext::tag("api")];
135/// let err = accumulate_context("unauthorized", contexts);
136/// assert_eq!(err.context().len(), 2);
137/// ```
138#[inline]
139pub fn accumulate_context<E, I, C>(error: E, contexts: I) -> ComposableError<E>
140where
141    I: IntoIterator<Item = C>,
142    C: IntoErrorContext,
143{
144    contexts
145        .into_iter()
146        .fold(ComposableError::new(error), |err, ctx| err.with_context(ctx))
147}
148
149/// Creates a reusable closure that wraps errors with multiple contexts.
150///
151/// Returns a function that can be used with `map_err` to attach the same
152/// set of contexts to multiple error paths.
153///
154/// # Arguments
155///
156/// * `contexts` - Iterator of contexts to attach (must be `Clone`)
157///
158/// # Examples
159///
160/// ```
161/// use error_rail::{context_accumulator, ErrorContext};
162///
163/// let contexts = vec![ErrorContext::tag("auth"), ErrorContext::tag("api")];
164/// let add_contexts = context_accumulator(contexts);
165/// let err = add_contexts("unauthorized");
166/// assert_eq!(err.context().len(), 2);
167/// ```
168#[inline]
169pub fn context_accumulator<E, I, C>(contexts: I) -> impl Fn(E) -> ComposableError<E>
170where
171    I: IntoIterator<Item = C> + Clone,
172    C: IntoErrorContext + Clone,
173{
174    move |error| accumulate_context(error, contexts.clone())
175}
176
177/// Formats a [`ComposableError`] as a human-readable error chain.
178///
179/// Returns a string representation showing all contexts and the core error.
180///
181/// # Arguments
182///
183/// * `error` - The composable error to format
184///
185/// # Examples
186///
187/// ```
188/// use error_rail::{ComposableError, ErrorContext, format_error_chain};
189///
190/// let err = ComposableError::new("failed")
191///     .with_context(ErrorContext::tag("db"));
192/// let chain = format_error_chain(&err);
193/// assert!(chain.contains("failed"));
194/// ```
195#[inline]
196pub fn format_error_chain<E>(error: &ComposableError<E>) -> String
197where
198    E: Display,
199{
200    error.error_chain()
201}
202
203/// Extracts all context entries from a [`ComposableError`].
204///
205/// Returns a vector containing all attached contexts.
206///
207/// # Arguments
208///
209/// * `error` - The composable error to extract from
210///
211/// # Examples
212///
213/// ```
214/// use error_rail::{ComposableError, ErrorContext, extract_context};
215///
216/// let err = ComposableError::new("failed")
217///     .with_context(ErrorContext::tag("db"));
218/// let contexts = extract_context(&err);
219/// assert_eq!(contexts.len(), 1);
220/// ```
221#[inline]
222pub fn extract_context<E>(error: &ComposableError<E>) -> ErrorVec<ErrorContext> {
223    error.context()
224}