Skip to main content

snafu/futures/
try_stream.rs

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