error_rail/async_ext/future_ext.rs
1//! Extension trait for `Future<Output = Result<T, E>>`.
2//!
3//! Provides `.ctx()` and `.with_ctx()` methods for futures, mirroring
4//! the sync `ResultExt` trait.
5
6use core::future::Future;
7
8use crate::traits::IntoErrorContext;
9
10use super::context_future::ContextFuture;
11
12/// Extension trait for attaching error context to async Result-returning futures.
13///
14/// This trait mirrors the sync [`ResultExt`](crate::traits::ResultExt) trait,
15/// providing the same `.ctx()` and `.with_ctx()` ergonomics for async code.
16///
17/// # Design Principles
18///
19/// - **Lazy attachment**: Context is only attached when an error occurs
20/// - **Zero-cost success path**: No allocation or context computation on success
21/// - **Familiar syntax**: Same method names as sync counterparts
22///
23/// # Examples
24///
25/// ## Basic Usage
26///
27/// ```rust,no_run
28/// use error_rail::prelude_async::*;
29///
30/// #[derive(Debug)]
31/// struct User;
32///
33/// #[derive(Debug)]
34/// struct ApiError;
35///
36/// async fn fetch_from_db(_id: u64) -> Result<User, ApiError> {
37/// Err(ApiError)
38/// }
39///
40/// async fn fetch_user(id: u64) -> BoxedResult<User, ApiError> {
41/// fetch_from_db(id)
42/// .ctx("fetching user")
43/// .await
44/// .map_err(Box::new)
45/// }
46/// ```
47///
48/// ## With Lazy Context
49///
50/// ```rust,no_run
51/// use error_rail::prelude_async::*;
52///
53/// #[derive(Debug)]
54/// struct Order;
55///
56/// #[derive(Debug)]
57/// struct OrderError;
58///
59/// async fn validate_order(_order_id: u64) -> Result<Order, OrderError> {
60/// Err(OrderError)
61/// }
62///
63/// async fn process_order(order_id: u64) -> BoxedResult<Order, OrderError> {
64/// validate_order(order_id)
65/// .with_ctx(|| format!("validating order {}", order_id))
66/// .await
67/// .map_err(Box::new)
68/// }
69/// ```
70pub trait FutureResultExt<T, E>: Future<Output = Result<T, E>> + Sized {
71 /// Attaches a static context to the future's error.
72 ///
73 /// The context is converted to an error context only when the future
74 /// resolves to an error.
75 ///
76 /// Note: if you pass an already-formatted `String` (e.g. `format!(...)`),
77 /// that formatting still happens eagerly before calling `.ctx()`.
78 ///
79 /// # Arguments
80 ///
81 /// * `context` - Any type implementing `IntoErrorContext`
82 ///
83 /// # Examples
84 ///
85 /// ```rust
86 /// use error_rail::prelude_async::*;
87 ///
88 /// async fn example() {
89 /// let result = async { Err::<(), _>("failed") }
90 /// .ctx("operation failed")
91 /// .await;
92 /// assert!(result.is_err());
93 /// }
94 /// ```
95 fn ctx<C>(self, context: C) -> ContextFuture<Self, impl FnOnce() -> C>
96 where
97 C: IntoErrorContext,
98 {
99 self.with_ctx(move || context)
100 }
101
102 /// Attaches a lazily-evaluated context to the future's error.
103 ///
104 /// The closure is only called when the future resolves to an error,
105 /// avoiding any computation on the success path.
106 ///
107 /// Use this (or `context!`) when you want lazy string formatting.
108 ///
109 /// # Arguments
110 ///
111 /// * `f` - A closure that produces the error context
112 ///
113 /// # Examples
114 ///
115 /// ```rust,no_run
116 /// use error_rail::prelude_async::*;
117 ///
118 /// #[derive(Debug)]
119 /// struct User;
120 ///
121 /// #[derive(Debug)]
122 /// struct ApiError;
123 ///
124 /// async fn fetch_from_db(_id: u64) -> Result<User, ApiError> {
125 /// Err(ApiError)
126 /// }
127 ///
128 /// async fn fetch_user(id: u64) -> BoxedResult<User, ApiError> {
129 /// fetch_from_db(id)
130 /// .with_ctx(|| format!("fetching user {}", id))
131 /// .await
132 /// .map_err(Box::new)
133 /// }
134 /// ```
135 fn with_ctx<F, C>(self, f: F) -> ContextFuture<Self, F>
136 where
137 F: FnOnce() -> C,
138 C: IntoErrorContext;
139}
140
141impl<Fut, T, E> FutureResultExt<T, E> for Fut
142where
143 Fut: Future<Output = Result<T, E>>,
144{
145 #[inline]
146 fn with_ctx<F, C>(self, f: F) -> ContextFuture<Self, F>
147 where
148 F: FnOnce() -> C,
149 C: IntoErrorContext,
150 {
151 ContextFuture::new(self, f)
152 }
153}