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