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}