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/// ```
138pub fn accumulate_context<E, I, C>(error: E, contexts: I) -> ComposableError<E>
139where
140    I: IntoIterator<Item = C>,
141    C: IntoErrorContext,
142{
143    let mut err = ComposableError::new(error);
144    for context in contexts {
145        err = err.with_context(context);
146    }
147    err
148}
149
150/// Creates a reusable closure that wraps errors with multiple contexts.
151///
152/// Returns a function that can be used with `map_err` to attach the same
153/// set of contexts to multiple error paths.
154///
155/// # Arguments
156///
157/// * `contexts` - Iterator of contexts to attach (must be `Clone`)
158///
159/// # Examples
160///
161/// ```
162/// use error_rail::{context_accumulator, ErrorContext};
163///
164/// let contexts = vec![ErrorContext::tag("auth"), ErrorContext::tag("api")];
165/// let add_contexts = context_accumulator(contexts);
166/// let err = add_contexts("unauthorized");
167/// assert_eq!(err.context().len(), 2);
168/// ```
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/// ```
195pub fn format_error_chain<E>(error: &ComposableError<E>) -> String
196where
197    E: Display,
198{
199    error.error_chain()
200}
201
202/// Extracts all context entries from a [`ComposableError`].
203///
204/// Returns a vector containing all attached contexts.
205///
206/// # Arguments
207///
208/// * `error` - The composable error to extract from
209///
210/// # Examples
211///
212/// ```
213/// use error_rail::{ComposableError, ErrorContext, extract_context};
214///
215/// let err = ComposableError::new("failed")
216///     .with_context(ErrorContext::tag("db"));
217/// let contexts = extract_context(&err);
218/// assert_eq!(contexts.len(), 1);
219/// ```
220pub fn extract_context<E>(error: &ComposableError<E>) -> ErrorVec<ErrorContext> {
221    error.context()
222}