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}