error_rail/async_ext/context_future.rs
1//! Future wrappers for lazy context evaluation.
2//!
3//! This module provides `ContextFuture`, which wraps a `Future<Output = Result<T, E>>`
4//! and attaches error context only when the future resolves to an error.
5
6use core::future::Future;
7use core::pin::Pin;
8use core::task::{Context, Poll};
9
10use pin_project_lite::pin_project;
11
12use crate::traits::IntoErrorContext;
13use crate::types::ComposableError;
14
15pin_project! {
16 /// A Future wrapper that attaches error context lazily.
17 ///
18 /// The context is only evaluated when the inner future resolves to an error,
19 /// maintaining zero-cost on the success path.
20 ///
21 /// # Cancel Safety
22 ///
23 /// `ContextFuture` is cancel-safe if the inner future is cancel-safe.
24 /// The `context_fn` is only called when `poll` returns `Poll::Ready(Err(_))`.
25 ///
26 /// # Examples
27 ///
28 /// ```rust
29 /// use error_rail::prelude_async::*;
30 ///
31 /// async fn example() -> BoxedResult<i32, &'static str> {
32 /// async { Err("failed") }
33 /// .ctx("operation context")
34 /// .await
35 /// .map_err(Box::new)
36 /// }
37 /// ```
38 #[must_use = "futures do nothing unless polled"]
39 pub struct ContextFuture<Fut, F> {
40 #[pin]
41 future: Fut,
42 context_fn: Option<F>,
43 }
44}
45
46impl<Fut, F> ContextFuture<Fut, F> {
47 /// Creates a new `ContextFuture` with the given future and context generator.
48 #[inline]
49 pub fn new(future: Fut, context_fn: F) -> Self {
50 Self { future, context_fn: Some(context_fn) }
51 }
52}
53
54impl<Fut, F, C, T, E> Future for ContextFuture<Fut, F>
55where
56 Fut: Future<Output = Result<T, E>>,
57 F: FnOnce() -> C,
58 C: IntoErrorContext,
59{
60 type Output = Result<T, ComposableError<E>>;
61
62 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
63 let this = self.project();
64
65 this.future.poll(cx).map(|res| {
66 res.map_err(|err| {
67 let context_fn = this
68 .context_fn
69 .take()
70 .expect("ContextFuture polled after completion");
71 ComposableError::new(err).with_context(context_fn())
72 })
73 })
74 }
75}