error_stack/
future.rs

1//! Extension for convenient usage of [`Report`]s returned by [`Future`] s.
2//!
3//! Extends [`Future`] with the same methods as [`ResultExt`] but calls the methods on [`poll`]ing.
4//!
5//! [`Report`]: crate::Report
6//! [`poll`]: Future::poll
7
8use core::{
9    fmt::{Debug, Display},
10    future::Future,
11    pin::Pin,
12    task::{Context as TaskContext, Poll},
13};
14
15use crate::{Context, Result, ResultExt};
16
17macro_rules! implement_future_adaptor {
18    ($future:ident, $method:ident, $bound:ident $(+ $bounds:ident)* $(+ $lifetime:lifetime)*, $output:ty) => {
19        #[doc = concat!("Adaptor returned by [`FutureExt::", stringify!( $method ), "`].")]
20        pub struct $future<Fut, T> {
21            future: Fut,
22            inner: Option<T>,
23        }
24
25        impl<Fut, T> Future for $future<Fut, T>
26        where
27            Fut: Future,
28            Fut::Output: ResultExt,
29            T: $bound $(+ $bounds)* $(+ $lifetime)*
30        {
31            type Output = $output;
32
33            #[track_caller]
34            fn poll(self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll<Self::Output> {
35                // SAFETY: The pointee of `inner` will not move. Note, that the value inside the
36                //         `Option` will be taken, but the `Option` remains in place. Additionally,
37                //         `Self` does not implement `Drop`, nor is it `#[repr(packed)]`
38                //         See the `pin` module: https://doc.rust-lang.org/core/pin/index.html
39                let (future, inner) = unsafe {
40                    let Self { future, inner } = self.get_unchecked_mut();
41                    (Pin::new_unchecked(future), inner)
42                };
43
44                // Can't use `map` as `#[track_caller]` is unstable on closures
45                match future.poll(cx) {
46                    Poll::Ready(value) => {
47                        Poll::Ready(value.$method({
48                            inner.take().expect("Cannot poll context after it resolves")
49                        }))
50                    }
51                    Poll::Pending => Poll::Pending,
52                }
53            }
54        }
55    };
56}
57
58macro_rules! implement_lazy_future_adaptor {
59    ($future:ident, $method:ident, $bound:ident $(+ $bounds:ident)* $(+ $lifetime:lifetime)*, $output:ty) => {
60        #[doc = concat!("Adaptor returned by [`FutureExt::", stringify!( $method ), "`].")]
61        pub struct $future<Fut, F> {
62            future: Fut,
63            inner: Option<F>,
64        }
65
66        impl<Fut, F, T> Future for $future<Fut, F>
67        where
68            Fut: Future,
69            Fut::Output: ResultExt,
70            F: FnOnce() -> T,
71            T: $bound $(+ $bounds)* $(+ $lifetime)*
72        {
73            type Output = $output;
74
75            #[track_caller]
76            fn poll(self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll<Self::Output> {
77                // SAFETY: The pointee of `inner` will not move. Note, that the value inside the
78                //         `Option` will be taken, but the `Option` remains in place. Additionally,
79                //         `Self` does not implement `Drop`, nor is it `#[repr(packed)]`
80                //         See the `pin` module: https://doc.rust-lang.org/core/pin/index.html
81                let (future, inner) = unsafe {
82                    let Self { future, inner } = self.get_unchecked_mut();
83                    (Pin::new_unchecked(future), inner)
84                };
85
86                // Can't use `map` as `#[track_caller]` is unstable on closures
87                match future.poll(cx) {
88                    Poll::Ready(value) => {
89                        Poll::Ready(value.$method({
90                            inner.take().expect("Cannot poll context after it resolves")
91                        }))
92                    }
93                    Poll::Pending => Poll::Pending,
94                }
95            }
96        }
97    };
98}
99
100implement_future_adaptor!(
101    FutureWithAttachment,
102    attach,
103    Send + Sync + 'static,
104    Result<<Fut::Output as ResultExt>::Ok, <Fut::Output as ResultExt>::Context>
105);
106
107implement_lazy_future_adaptor!(
108    FutureWithLazyAttachment,
109    attach_lazy,
110    Send + Sync + 'static,
111    Result<<Fut::Output as ResultExt>::Ok, <Fut::Output as ResultExt>::Context>
112);
113
114implement_future_adaptor!(
115    FutureWithPrintableAttachment,
116    attach_printable,
117    Display + Debug + Send + Sync + 'static,
118    Result<<Fut::Output as ResultExt>::Ok, <Fut::Output as ResultExt>::Context>
119);
120
121implement_lazy_future_adaptor!(
122    FutureWithLazyPrintableAttachment,
123    attach_printable_lazy,
124    Display + Debug + Send + Sync + 'static,
125    Result<<Fut::Output as ResultExt>::Ok, <Fut::Output as ResultExt>::Context>
126);
127
128implement_future_adaptor!(
129    FutureWithContext,
130    change_context,
131    Context,
132    Result<<Fut::Output as ResultExt>::Ok, T>
133);
134
135implement_lazy_future_adaptor!(
136    FutureWithLazyContext,
137    change_context_lazy,
138    Context,
139    Result<<Fut::Output as ResultExt>::Ok, T>
140);
141
142/// Extension trait for [`Future`] to provide contextual information on [`Report`]s.
143///
144/// [`Report`]: crate::Report
145pub trait FutureExt: Future + Sized {
146    /// Adds a new attachment to the [`Report`] inside the [`Result`] when [`poll`]ing the
147    /// [`Future`].
148    ///
149    /// Applies [`Report::attach`] on the [`Err`] variant, refer to it for more information.
150    ///
151    /// [`Report`]: crate::Report
152    /// [`Report::attach`]: crate::Report::attach
153    /// [`poll`]: Future::poll
154    #[track_caller]
155    fn attach<A>(self, attachment: A) -> FutureWithAttachment<Self, A>
156    where
157        A: Send + Sync + 'static;
158
159    /// Lazily adds a new attachment to the [`Report`] inside the [`Result`] when [`poll`]ing the
160    /// [`Future`].
161    ///
162    /// Applies [`Report::attach`] on the [`Err`] variant, refer to it for more information.
163    ///
164    /// [`Report`]: crate::Report
165    /// [`Report::attach`]: crate::Report::attach
166    /// [`poll`]: Future::poll
167    #[track_caller]
168    fn attach_lazy<A, F>(self, attachment: F) -> FutureWithLazyAttachment<Self, F>
169    where
170        A: Send + Sync + 'static,
171        F: FnOnce() -> A;
172
173    /// Adds a new printable attachment to the [`Report`] inside the [`Result`] when [`poll`]ing the
174    /// [`Future`].
175    ///
176    /// Applies [`Report::attach_printable`] on the [`Err`] variant, refer to it for more
177    /// information.
178    ///
179    /// [`Report`]: crate::Report
180    /// [`Report::attach_printable`]: crate::Report::attach_printable
181    /// [`poll`]: Future::poll
182    #[track_caller]
183    fn attach_printable<A>(self, attachment: A) -> FutureWithPrintableAttachment<Self, A>
184    where
185        A: Display + Debug + Send + Sync + 'static;
186
187    /// Lazily adds a new printable attachment to the [`Report`] inside the [`Result`] when
188    /// [`poll`]ing the [`Future`].
189    ///
190    /// Applies [`Report::attach_printable`] on the [`Err`] variant, refer to it for more
191    /// information.
192    ///
193    /// [`Report`]: crate::Report
194    /// [`Report::attach_printable`]: crate::Report::attach_printable
195    /// [`poll`]: Future::poll
196    #[track_caller]
197    fn attach_printable_lazy<A, F>(
198        self,
199        attachment: F,
200    ) -> FutureWithLazyPrintableAttachment<Self, F>
201    where
202        A: Display + Debug + Send + Sync + 'static,
203        F: FnOnce() -> A;
204
205    /// Changes the [`Context`] of the [`Report`] inside the [`Result`] when [`poll`]ing the
206    /// [`Future`].
207    ///
208    /// Applies [`Report::change_context`] on the [`Err`] variant, refer to it for more information.
209    ///
210    /// [`Report`]: crate::Report
211    /// [`Report::change_context`]: crate::Report::change_context
212    /// [`poll`]: Future::poll
213    #[track_caller]
214    fn change_context<C>(self, context: C) -> FutureWithContext<Self, C>
215    where
216        C: Context;
217
218    /// Lazily changes the [`Context`] of the [`Report`] inside the [`Result`] when [`poll`]ing the
219    /// [`Future`].
220    ///
221    /// Applies [`Report::change_context`] on the [`Err`] variant, refer to it for more information.
222    ///
223    /// [`Report`]: crate::Report
224    /// [`Report::change_context`]: crate::Report::change_context
225    /// [`poll`]: Future::poll
226    #[track_caller]
227    fn change_context_lazy<C, F>(self, context: F) -> FutureWithLazyContext<Self, F>
228    where
229        C: Context,
230        F: FnOnce() -> C;
231}
232
233impl<Fut: Future> FutureExt for Fut
234where
235    Fut::Output: ResultExt,
236{
237    fn attach<A>(self, attachment: A) -> FutureWithAttachment<Self, A>
238    where
239        A: Send + Sync + 'static,
240    {
241        FutureWithAttachment {
242            future: self,
243            inner: Some(attachment),
244        }
245    }
246
247    #[track_caller]
248    fn attach_lazy<A, F>(self, attachment: F) -> FutureWithLazyAttachment<Self, F>
249    where
250        A: Send + Sync + 'static,
251        F: FnOnce() -> A,
252    {
253        FutureWithLazyAttachment {
254            future: self,
255            inner: Some(attachment),
256        }
257    }
258
259    #[track_caller]
260    fn attach_printable<A>(self, attachment: A) -> FutureWithPrintableAttachment<Self, A>
261    where
262        A: Display + Debug + Send + Sync + 'static,
263    {
264        FutureWithPrintableAttachment {
265            future: self,
266            inner: Some(attachment),
267        }
268    }
269
270    #[track_caller]
271    fn attach_printable_lazy<A, F>(
272        self,
273        attachment: F,
274    ) -> FutureWithLazyPrintableAttachment<Self, F>
275    where
276        A: Display + Debug + Send + Sync + 'static,
277        F: FnOnce() -> A,
278    {
279        FutureWithLazyPrintableAttachment {
280            future: self,
281            inner: Some(attachment),
282        }
283    }
284
285    #[track_caller]
286    fn change_context<C>(self, context: C) -> FutureWithContext<Self, C>
287    where
288        C: Context,
289    {
290        FutureWithContext {
291            future: self,
292            inner: Some(context),
293        }
294    }
295
296    #[track_caller]
297    fn change_context_lazy<C, F>(self, context: F) -> FutureWithLazyContext<Self, F>
298    where
299        C: Context,
300        F: FnOnce() -> C,
301    {
302        FutureWithLazyContext {
303            future: self,
304            inner: Some(context),
305        }
306    }
307}