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}