Skip to main content

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    error::Error,
10    future::Future,
11    pin::Pin,
12    task::{Context as TaskContext, Poll},
13};
14
15use crate::{Attachment, OpaqueAttachment, Report, 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    Attachment,
104    Result<<Fut::Output as ResultExt>::Ok, Report<<Fut::Output as ResultExt>::Context>>
105);
106
107implement_lazy_future_adaptor!(
108    FutureWithLazyAttachment,
109    attach_with,
110    Attachment,
111    Result<<Fut::Output as ResultExt>::Ok, Report<<Fut::Output as ResultExt>::Context>>
112);
113
114implement_future_adaptor!(
115    FutureWithOpaqueAttachment,
116    attach_opaque,
117    OpaqueAttachment,
118    Result<<Fut::Output as ResultExt>::Ok, Report<<Fut::Output as ResultExt>::Context>>
119);
120
121implement_lazy_future_adaptor!(
122    FutureWithLazyOpaqueAttachment,
123    attach_opaque_with,
124    OpaqueAttachment,
125    Result<<Fut::Output as ResultExt>::Ok, Report<<Fut::Output as ResultExt>::Context>>
126);
127
128implement_future_adaptor!(
129    FutureWithContext,
130    change_context,
131    Error + Send + Sync + 'static,
132    Result<<Fut::Output as ResultExt>::Ok, Report<T>>
133);
134
135implement_lazy_future_adaptor!(
136    FutureWithLazyContext,
137    change_context_lazy,
138    Error + Send + Sync + 'static,
139    Result<<Fut::Output as ResultExt>::Ok, Report<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 printable 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
150    /// information.
151    ///
152    /// [`Report`]: crate::Report
153    /// [`Report::attach`]: crate::Report::attach
154    /// [`poll`]: Future::poll
155    #[track_caller]
156    fn attach<A>(self, attachment: A) -> FutureWithAttachment<Self, A>
157    where
158        A: Attachment;
159
160    /// Lazily adds a new printable attachment to the [`Report`] inside the [`Result`] when
161    /// [`poll`]ing the [`Future`].
162    ///
163    /// Applies [`Report::attach`] on the [`Err`] variant, refer to it for more
164    /// information.
165    ///
166    /// [`Report`]: crate::Report
167    /// [`Report::attach`]: crate::Report::attach
168    /// [`poll`]: Future::poll
169    #[track_caller]
170    fn attach_with<A, F>(self, attachment: F) -> FutureWithLazyAttachment<Self, F>
171    where
172        A: Attachment,
173        F: FnOnce() -> A;
174
175    /// Adds a new attachment to the [`Report`] inside the [`Result`] when [`poll`]ing the
176    /// [`Future`].
177    ///
178    /// Applies [`Report::attach_opaque`] on the [`Err`] variant, refer to it for more information.
179    ///
180    /// [`Report`]: crate::Report
181    /// [`Report::attach_opaque`]: crate::Report::attach_opaque
182    /// [`poll`]: Future::poll
183    #[track_caller]
184    fn attach_opaque<A>(self, attachment: A) -> FutureWithOpaqueAttachment<Self, A>
185    where
186        A: OpaqueAttachment;
187
188    /// Lazily adds a new attachment to the [`Report`] inside the [`Result`] when [`poll`]ing the
189    /// [`Future`].
190    ///
191    /// Applies [`Report::attach_opaque`] on the [`Err`] variant, refer to it for more information.
192    ///
193    /// [`Report`]: crate::Report
194    /// [`Report::attach_opaque`]: crate::Report::attach_opaque
195    /// [`poll`]: Future::poll
196    #[track_caller]
197    fn attach_opaque_with<A, F>(self, attachment: F) -> FutureWithLazyOpaqueAttachment<Self, F>
198    where
199        A: OpaqueAttachment,
200        F: FnOnce() -> A;
201
202    /// Changes the [`Error`] context of the [`Report`] inside the [`Result`] when [`poll`]ing the
203    /// [`Future`].
204    ///
205    /// Applies [`Report::change_context`] on the [`Err`] variant, refer to it for more information.
206    ///
207    /// [`Report`]: crate::Report
208    /// [`Report::change_context`]: crate::Report::change_context
209    /// [`poll`]: Future::poll
210    #[track_caller]
211    fn change_context<C>(self, context: C) -> FutureWithContext<Self, C>
212    where
213        C: Error + Send + Sync + 'static;
214
215    /// Lazily changes the [`Error`] context of the [`Report`] inside the [`Result`] when
216    /// [`poll`]ing the [`Future`].
217    ///
218    /// Applies [`Report::change_context`] on the [`Err`] variant, refer to it for more information.
219    ///
220    /// [`Report`]: crate::Report
221    /// [`Report::change_context`]: crate::Report::change_context
222    /// [`poll`]: Future::poll
223    #[track_caller]
224    fn change_context_lazy<C, F>(self, context: F) -> FutureWithLazyContext<Self, F>
225    where
226        C: Error + Send + Sync + 'static,
227        F: FnOnce() -> C;
228}
229
230impl<Fut: Future> FutureExt for Fut
231where
232    Fut::Output: ResultExt,
233{
234    #[track_caller]
235    fn attach<A>(self, attachment: A) -> FutureWithAttachment<Self, A>
236    where
237        A: Attachment,
238    {
239        FutureWithAttachment {
240            future: self,
241            inner: Some(attachment),
242        }
243    }
244
245    #[track_caller]
246    fn attach_with<A, F>(self, attachment: F) -> FutureWithLazyAttachment<Self, F>
247    where
248        A: Attachment,
249        F: FnOnce() -> A,
250    {
251        FutureWithLazyAttachment {
252            future: self,
253            inner: Some(attachment),
254        }
255    }
256
257    fn attach_opaque<A>(self, attachment: A) -> FutureWithOpaqueAttachment<Self, A>
258    where
259        A: OpaqueAttachment,
260    {
261        FutureWithOpaqueAttachment {
262            future: self,
263            inner: Some(attachment),
264        }
265    }
266
267    #[track_caller]
268    fn attach_opaque_with<A, F>(self, attachment: F) -> FutureWithLazyOpaqueAttachment<Self, F>
269    where
270        A: OpaqueAttachment,
271        F: FnOnce() -> A,
272    {
273        FutureWithLazyOpaqueAttachment {
274            future: self,
275            inner: Some(attachment),
276        }
277    }
278
279    #[track_caller]
280    fn change_context<C>(self, context: C) -> FutureWithContext<Self, C>
281    where
282        C: Error + Send + Sync + 'static,
283    {
284        FutureWithContext {
285            future: self,
286            inner: Some(context),
287        }
288    }
289
290    #[track_caller]
291    fn change_context_lazy<C, F>(self, context: F) -> FutureWithLazyContext<Self, F>
292    where
293        C: Error + Send + Sync + 'static,
294        F: FnOnce() -> C,
295    {
296        FutureWithLazyContext {
297            future: self,
298            inner: Some(context),
299        }
300    }
301}