Skip to main content

snafu/futures/
try_future.rs

1//! Additions to the [`TryFuture`] trait.
2//!
3//! [`TryFuture`]: futures_core::future::TryFuture
4
5use crate::{Error, ErrorCompat, IntoError};
6use core::{
7    future::Future,
8    marker::PhantomData,
9    pin::Pin,
10    task::{Context as TaskContext, Poll},
11};
12use futures_core::future::TryFuture;
13use pin_project::pin_project;
14
15#[cfg(feature = "alloc")]
16use alloc::string::String;
17
18#[cfg(feature = "alloc")]
19use crate::FromString;
20
21/// Additions to [`TryFuture`].
22pub trait TryFutureExt: TryFuture + Sized {
23    /// Extend a [`TryFuture`]'s error with additional context-sensitive
24    /// information.
25    ///
26    /// ```rust
27    /// # #[cfg(feature = "internal-dev-dependencies")] {
28    /// use futures::future::TryFuture;
29    /// use snafu::prelude::*;
30    ///
31    /// #[derive(Debug, Snafu)]
32    /// enum Error {
33    ///     Authenticating {
34    ///         user_name: String,
35    ///         user_id: i32,
36    ///         source: ApiError,
37    ///     },
38    /// }
39    ///
40    /// fn example() -> impl TryFuture<Ok = i32, Error = Error> {
41    ///     another_function().context(AuthenticatingSnafu {
42    ///         user_name: "admin",
43    ///         user_id: 42,
44    ///     })
45    /// }
46    ///
47    /// # type ApiError = Box<dyn std::error::Error>;
48    /// fn another_function() -> impl TryFuture<Ok = i32, Error = ApiError> {
49    ///     /* ... */
50    /// # futures::future::ok(42)
51    /// }
52    /// # }
53    /// ```
54    ///
55    /// Note that the context selector will call [`Into::into`] on
56    /// each field, so the types are not required to exactly match.
57    fn context<C, E>(self, context: C) -> Context<Self, C, E>
58    where
59        C: IntoError<E, Source = Self::Error>,
60        E: Error + ErrorCompat;
61
62    /// Extend a [`TryFuture`]'s error with lazily-generated context-sensitive
63    /// information.
64    ///
65    /// ```rust
66    /// # #[cfg(feature = "internal-dev-dependencies")] {
67    /// use futures::future::TryFuture;
68    /// use snafu::prelude::*;
69    ///
70    /// #[derive(Debug, Snafu)]
71    /// enum Error {
72    ///     Authenticating {
73    ///         user_name: String,
74    ///         user_id: i32,
75    ///         source: ApiError,
76    ///     },
77    /// }
78    ///
79    /// fn example() -> impl TryFuture<Ok = i32, Error = Error> {
80    ///     another_function().with_context(|_| AuthenticatingSnafu {
81    ///         user_name: "admin".to_string(),
82    ///         user_id: 42,
83    ///     })
84    /// }
85    ///
86    /// # type ApiError = Box<dyn std::error::Error>;
87    /// fn another_function() -> impl TryFuture<Ok = i32, Error = ApiError> {
88    ///     /* ... */
89    /// # futures::future::ok(42)
90    /// }
91    /// # }
92    /// ```
93    ///
94    /// Note that this *may not* be needed in many cases because the
95    /// context selector will call [`Into::into`] on each field.
96    fn with_context<F, C, E>(self, context: F) -> WithContext<Self, F, E>
97    where
98        F: FnOnce(&mut Self::Error) -> C,
99        C: IntoError<E, Source = Self::Error>,
100        E: Error + ErrorCompat;
101
102    /// Extend a [`TryFuture`]'s error with information from a string.
103    ///
104    /// The target error type must implement [`FromString`] by using
105    /// the
106    /// [`#[snafu(whatever)]`][crate::Snafu#controlling-stringly-typed-errors]
107    /// attribute. The premade [`Whatever`](crate::Whatever) type is also available.
108    ///
109    /// In many cases, you will want to use
110    /// [`with_whatever_context`][Self::with_whatever_context] instead
111    /// as it is only called in case of error. This method is best
112    /// suited for when you have a string literal.
113    ///
114    /// ```rust
115    /// # #[cfg(feature = "internal-dev-dependencies")] {
116    /// use futures::future::TryFuture;
117    /// use snafu::{prelude::*, Whatever};
118    ///
119    /// fn example() -> impl TryFuture<Ok = i32, Error = Whatever> {
120    ///     api_function().whatever_context("The API failed")
121    /// }
122    ///
123    /// # type ApiError = Box<dyn std::error::Error + Send + Sync>;
124    /// fn api_function() -> impl TryFuture<Ok = i32, Error = ApiError> {
125    ///     /* ... */
126    /// # futures::future::ok(42)
127    /// }
128    /// # }
129    /// ```
130    #[cfg(any(feature = "alloc", test))]
131    fn whatever_context<S, E>(self, context: S) -> WhateverContext<Self, S, E>
132    where
133        S: Into<String>,
134        E: FromString;
135
136    /// Extend a [`TryFuture`]'s error with information from a
137    /// lazily-generated string.
138    ///
139    /// The target error type must implement [`FromString`] by using
140    /// the
141    /// [`#[snafu(whatever)]`][crate::Snafu#controlling-stringly-typed-errors]
142    /// attribute. The premade [`Whatever`](crate::Whatever) type is also available.
143    ///
144    /// ```rust
145    /// # #[cfg(feature = "internal-dev-dependencies")] {
146    /// use futures::future::TryFuture;
147    /// use snafu::{prelude::*, Whatever};
148    ///
149    /// fn example(arg: &'static str) -> impl TryFuture<Ok = i32, Error = Whatever> {
150    ///     api_function(arg)
151    ///         .with_whatever_context(move |_| format!("The API failed for argument {arg}"))
152    /// }
153    ///
154    /// # type ApiError = Box<dyn std::error::Error + Send + Sync>;
155    /// fn api_function(arg: &'static str) -> impl TryFuture<Ok = i32, Error = ApiError> {
156    ///     /* ... */
157    /// # futures::future::ok(42)
158    /// }
159    /// # }
160    /// ```
161    #[cfg(any(feature = "alloc", test))]
162    fn with_whatever_context<F, S, E>(self, context: F) -> WithWhateverContext<Self, F, E>
163    where
164        F: FnOnce(&mut Self::Error) -> S,
165        S: Into<String>,
166        E: FromString;
167}
168
169impl<Fut> TryFutureExt for Fut
170where
171    Fut: TryFuture,
172{
173    fn context<C, E>(self, context: C) -> Context<Self, C, E>
174    where
175        C: IntoError<E, Source = Self::Error>,
176        E: Error + ErrorCompat,
177    {
178        Context {
179            inner: self,
180            context: Some(context),
181            _e: PhantomData,
182        }
183    }
184
185    fn with_context<F, C, E>(self, context: F) -> WithContext<Self, F, E>
186    where
187        F: FnOnce(&mut Self::Error) -> C,
188        C: IntoError<E, Source = Self::Error>,
189        E: Error + ErrorCompat,
190    {
191        WithContext {
192            inner: self,
193            context: Some(context),
194            _e: PhantomData,
195        }
196    }
197
198    #[cfg(any(feature = "alloc", test))]
199    fn whatever_context<S, E>(self, context: S) -> WhateverContext<Self, S, E>
200    where
201        S: Into<String>,
202        E: FromString,
203    {
204        WhateverContext {
205            inner: self,
206            context: Some(context),
207            _e: PhantomData,
208        }
209    }
210
211    #[cfg(any(feature = "alloc", test))]
212    fn with_whatever_context<F, S, E>(self, context: F) -> WithWhateverContext<Self, F, E>
213    where
214        F: FnOnce(&mut Self::Error) -> S,
215        S: Into<String>,
216        E: FromString,
217    {
218        WithWhateverContext {
219            inner: self,
220            context: Some(context),
221            _e: PhantomData,
222        }
223    }
224}
225
226/// Future for the [`context`](TryFutureExt::context) combinator.
227///
228/// See the [`TryFutureExt::context`] method for more details.
229#[pin_project]
230#[derive(Debug)]
231#[must_use = "futures do nothing unless polled"]
232pub struct Context<Fut, C, E> {
233    #[pin]
234    inner: Fut,
235    context: Option<C>,
236    _e: PhantomData<E>,
237}
238
239impl<Fut, C, E> Future for Context<Fut, C, E>
240where
241    Fut: TryFuture,
242    C: IntoError<E, Source = Fut::Error>,
243    E: Error + ErrorCompat,
244{
245    type Output = Result<Fut::Ok, E>;
246
247    #[track_caller]
248    fn poll(self: Pin<&mut Self>, ctx: &mut TaskContext) -> Poll<Self::Output> {
249        let this = self.project();
250        let inner = this.inner;
251        let context = this.context;
252
253        // https://github.com/rust-lang/rust/issues/74042
254        match inner.try_poll(ctx) {
255            Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)),
256            Poll::Ready(Err(error)) => {
257                let error = context
258                    .take()
259                    .expect("Cannot poll Context after it resolves")
260                    .into_error(error);
261                Poll::Ready(Err(error))
262            }
263            Poll::Pending => Poll::Pending,
264        }
265    }
266}
267
268/// Future for the [`with_context`](TryFutureExt::with_context) combinator.
269///
270/// See the [`TryFutureExt::with_context`] method for more details.
271#[pin_project]
272#[derive(Debug)]
273#[must_use = "futures do nothing unless polled"]
274pub struct WithContext<Fut, F, E> {
275    #[pin]
276    inner: Fut,
277    context: Option<F>,
278    _e: PhantomData<E>,
279}
280
281impl<Fut, F, C, E> Future for WithContext<Fut, F, E>
282where
283    Fut: TryFuture,
284    F: FnOnce(&mut Fut::Error) -> C,
285    C: IntoError<E, Source = Fut::Error>,
286    E: Error + ErrorCompat,
287{
288    type Output = Result<Fut::Ok, E>;
289
290    #[track_caller]
291    fn poll(self: Pin<&mut Self>, ctx: &mut TaskContext) -> Poll<Self::Output> {
292        let this = self.project();
293        let inner = this.inner;
294        let context = this.context;
295
296        // https://github.com/rust-lang/rust/issues/74042
297        match inner.try_poll(ctx) {
298            Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)),
299            Poll::Ready(Err(mut error)) => {
300                let context = context
301                    .take()
302                    .expect("Cannot poll WithContext after it resolves");
303
304                let error = context(&mut error).into_error(error);
305
306                Poll::Ready(Err(error))
307            }
308            Poll::Pending => Poll::Pending,
309        }
310    }
311}
312
313/// Future for the
314/// [`whatever_context`](TryFutureExt::whatever_context) combinator.
315///
316/// See the [`TryFutureExt::whatever_context`] method for more
317/// details.
318#[pin_project]
319#[derive(Debug)]
320#[must_use = "futures do nothing unless polled"]
321#[cfg(any(feature = "alloc", test))]
322pub struct WhateverContext<Fut, S, E> {
323    #[pin]
324    inner: Fut,
325    context: Option<S>,
326    _e: PhantomData<E>,
327}
328
329#[cfg(any(feature = "alloc", test))]
330impl<Fut, S, E> Future for WhateverContext<Fut, S, E>
331where
332    Fut: TryFuture,
333    S: Into<String>,
334    E: FromString,
335    Fut::Error: Into<E::Source>,
336{
337    type Output = Result<Fut::Ok, E>;
338
339    #[track_caller]
340    fn poll(self: Pin<&mut Self>, ctx: &mut TaskContext) -> Poll<Self::Output> {
341        let this = self.project();
342        let inner = this.inner;
343        let context = this.context;
344
345        // https://github.com/rust-lang/rust/issues/74042
346        match inner.try_poll(ctx) {
347            Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)),
348            Poll::Ready(Err(error)) => {
349                let context = context
350                    .take()
351                    .expect("Cannot poll WhateverContext after it resolves");
352                let error = FromString::with_source(error.into(), context.into());
353
354                Poll::Ready(Err(error))
355            }
356            Poll::Pending => Poll::Pending,
357        }
358    }
359}
360
361/// Future for the
362/// [`with_whatever_context`](TryFutureExt::with_whatever_context)
363/// combinator.
364///
365/// See the [`TryFutureExt::with_whatever_context`] method for more
366/// details.
367#[pin_project]
368#[derive(Debug)]
369#[must_use = "futures do nothing unless polled"]
370#[cfg(any(feature = "alloc", test))]
371pub struct WithWhateverContext<Fut, F, E> {
372    #[pin]
373    inner: Fut,
374    context: Option<F>,
375    _e: PhantomData<E>,
376}
377
378#[cfg(any(feature = "alloc", test))]
379impl<Fut, F, S, E> Future for WithWhateverContext<Fut, F, E>
380where
381    Fut: TryFuture,
382    F: FnOnce(&mut Fut::Error) -> S,
383    S: Into<String>,
384    E: FromString,
385    Fut::Error: Into<E::Source>,
386{
387    type Output = Result<Fut::Ok, E>;
388
389    #[track_caller]
390    fn poll(self: Pin<&mut Self>, ctx: &mut TaskContext) -> Poll<Self::Output> {
391        let this = self.project();
392        let inner = this.inner;
393        let context = this.context;
394
395        // https://github.com/rust-lang/rust/issues/74042
396        match inner.try_poll(ctx) {
397            Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)),
398            Poll::Ready(Err(mut error)) => {
399                let context = context
400                    .take()
401                    .expect("Cannot poll WhateverContext after it resolves");
402                let context = context(&mut error);
403                let error = FromString::with_source(error.into(), context.into());
404
405                Poll::Ready(Err(error))
406            }
407            Poll::Pending => Poll::Pending,
408        }
409    }
410}