type_toppings/
lib.rs

1//! # Type Toppings: Extensions for Standard Rust Types
2//!
3//! Opinionated collection of utility extensions for several of Rust's standard types, including:
4//! - `Result`
5//! - `Iterator`
6//! - `futures::Steam`
7//!
8//! # Examples:
9//!
10//! ```
11//! #[cfg(feature = "iterator")]
12//! {
13//!     use type_toppings::IteratorExt;
14//!    
15//!     // Map only the Some values in an iterator of Option<T>:
16//!     let data: Vec<_> = vec![Some(1), None, Some(3)]
17//!         .into_iter()
18//!         .map_opt(|x| x * 2)
19//!         .collect();
20//!     assert_eq!(data, vec![Some(2), None, Some(6)]);
21//! }
22//! ```
23//!
24//! For more detailed examples, see the documentation for each trait and method.
25
26#![cfg_attr(docsrs, feature(doc_auto_cfg))]
27
28#[cfg(feature = "iterator")]
29mod iterator;
30
31#[cfg(feature = "result")]
32mod result;
33
34#[cfg(feature = "stream")]
35mod stream;
36
37/// [`std::result::Result`] extensions.
38///
39/// Methods for the `Result` type for more descriptive unwrapping and error handling patterns.
40#[cfg(feature = "result")]
41pub trait ResultExt {
42    /// Success value
43    type T;
44
45    /// Error value
46    type E;
47
48    /// Unwraps the result, yielding the content of an [`Ok`].
49    ///
50    /// The closure `f` is only evaluated if the result contains an error.
51    ///
52    /// # Panics
53    ///
54    /// Panics if the value is an [`Err`], with a panic message provided by
55    /// the closure `f`.
56    ///
57    /// # Examples
58    ///
59    /// ```should_panic
60    /// # use type_toppings::ResultExt as _;
61    /// let x: Result<u32, &str> = Err("emergency failure");
62    /// x.expect_with(|| "Testing expect_with");
63    /// ```
64    #[track_caller]
65    fn expect_with<M, F: FnOnce() -> M>(self, f: F) -> Self::T
66    where
67        Self::E: std::fmt::Debug,
68        M: AsRef<str>;
69
70    /// Unwraps the result, yielding the content of an [`Ok`].
71    ///
72    /// # Panics
73    ///
74    /// Panics if the value is an [`Err`], with a panic message given as `msg`
75    /// and followed by a report of the full error chain.
76    ///
77    /// # Examples
78    ///
79    /// ```should_panic
80    /// # use type_toppings::ResultExt as _;
81    /// #[derive(Debug, derive_more::Error, derive_more::Display)]
82    /// #[display("Top-level error")]
83    /// struct TopError(SubError);
84    ///
85    /// #[derive(Debug, derive_more::Error, derive_more::Display)]
86    /// #[display("Sub-level error")]
87    /// struct SubError;
88    ///
89    /// let x: Result<u32, TopError> = Err(TopError(SubError));
90    /// x.expect_or_report("Failure detected");
91    /// ```
92    /// The above panics with
93    /// ```text
94    /// Failure detected: Top-level error
95    ///
96    /// Caused by:
97    ///       Sub-level error
98    /// ```
99    #[track_caller]
100    fn expect_or_report(self, msg: &str) -> Self::T
101    where
102        Self::E: std::error::Error;
103
104    /// Unwraps the result, yielding the content of an [`Ok`].
105    ///
106    /// # Panics
107    ///
108    /// Panics if the value is an [`Err`], with a panic message provided by
109    /// the closure `f` and followed by a report of the full error chain.
110    ///
111    /// # Examples
112    ///
113    /// ```should_panic
114    /// # use type_toppings::ResultExt as _;
115    /// #[derive(Debug, derive_more::Error, derive_more::Display)]
116    /// #[display("Top-level error")]
117    /// struct TopError(SubError);
118    ///
119    /// #[derive(Debug, derive_more::Error, derive_more::Display)]
120    /// #[display("Sub-level error")]
121    /// struct SubError;
122    ///
123    /// let x: Result<u32, TopError> = Err(TopError(SubError));
124    /// x.expect_or_report_with(|| "Dynamic failure detected");
125    /// ```
126    #[track_caller]
127    fn expect_or_report_with<M, F: FnOnce() -> M>(self, f: F) -> Self::T
128    where
129        Self::E: std::error::Error,
130        M: AsRef<str>;
131
132    /// Unwraps the result, yielding the content of an [`Ok`].
133    ///
134    /// # Panics
135    ///
136    /// Panics if the value is an [`Err`], with a report of the full error chain.
137    ///
138    /// # Examples
139    ///
140    /// ```should_panic
141    /// # use type_toppings::ResultExt as _;
142    /// #[derive(Debug, derive_more::Error, derive_more::Display)]
143    /// #[display("Top-level error")]
144    /// struct TopError(SubError);
145    ///
146    /// #[derive(Debug, derive_more::Error, derive_more::Display)]
147    /// #[display("Sub-level error")]
148    /// struct SubError;
149    ///
150    /// let x: Result<u32, TopError> = Err(TopError(SubError));
151    /// x.unwrap_or_report();
152    /// ```
153    #[track_caller]
154    fn unwrap_or_report(self) -> Self::T
155    where
156        Self::E: std::error::Error;
157}
158
159/// [`futures::Stream`] extensions.
160#[cfg(feature = "stream")]
161pub trait StreamExt {
162    /// Chains a single ready item to the end of the stream.
163    ///
164    /// This method appends a ready item to the stream, effectively increasing the length of the
165    /// stream by one. The item will be yielded after all items from the original stream.
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// # use type_toppings::StreamExt as _;
171    /// let initial_stream = futures::stream::iter(vec![1, 2, 3]);
172    /// let chained_stream = initial_stream.chain_ready(4);
173    ///
174    /// let collected: Vec<_> = futures::executor::block_on_stream(chained_stream).collect();
175    /// assert_eq!(collected, vec![1, 2, 3, 4]);
176    /// ```
177    fn chain_ready<T>(self, item: T) -> futures::stream::Chain<Self, futures::stream::Once<std::future::Ready<T>>>
178    where
179        Self: Sized,
180        Self: futures::Stream<Item = T>;
181
182    /// Chains a single future to the end of the stream.
183    ///
184    /// This method appends a future to the stream. When polled, the future will be awaited, and
185    /// its resulting item will be yielded after all items from the original stream.
186    ///
187    /// # Examples
188    ///
189    /// ```
190    /// # use type_toppings::StreamExt as _;
191    /// let initial_stream = futures::stream::iter(vec![1, 2, 3]);
192    /// let chained_stream = initial_stream.chain_future(Box::pin(async { 4 }));
193    ///
194    /// let collected: Vec<_> = futures::executor::block_on_stream(chained_stream).collect();
195    /// assert_eq!(collected, vec![1, 2, 3, 4]);
196    /// ```
197    fn chain_future<T, F>(self, fut: F) -> futures::stream::Chain<Self, futures::stream::Once<F>>
198    where
199        Self: Sized,
200        Self: futures::Stream<Item = T>,
201        F: core::future::Future<Output = T>;
202}
203
204/// [`std::iter::Iterator`] extensions.
205#[cfg(feature = "iterator")]
206pub trait IteratorExt {
207    /// Transforms the items in the iterator using the `Into` trait to convert
208    /// from `T` to `U`.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// # use type_toppings::IteratorExt;
214    /// let data: Vec<_> = vec![1_u8, 3_u8]
215    ///     .into_iter()
216    ///     .map_into::<i32>()
217    ///     .collect();
218    /// assert_eq!(data, vec![1_i32, 3_i32]);
219    /// ```
220    fn map_into<U>(self) -> iterator::map_into::MapInto<Self, U>
221    where
222        Self: Sized,
223        Self: Iterator,
224        <Self as Iterator>::Item: Into<U>;
225
226    /// Transforms the `Some` values in iterators of `Option<T>` using the given function `f`.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// # use type_toppings::IteratorExt;
232    /// let data: Vec<_> = vec![Some(1), None, Some(3)].into_iter().map_opt(|x| x * 2).collect();
233    /// assert_eq!(data, vec![Some(2), None, Some(6)]);
234    /// ```
235    fn map_opt<T, U, F>(self, f: F) -> iterator::map_opt::MapOpt<Self, F>
236    where
237        Self: Sized,
238        Self: Iterator<Item = Option<T>>,
239        F: FnMut(T) -> U;
240
241    /// Transforms the `Ok` values in iterators of `Result<T, E>` using the given function `f`.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// # use type_toppings::IteratorExt;
247    /// let data = [Ok(1), Err("some error"), Ok(3)]
248    ///     .into_iter()
249    ///     .map_res(|x| x * 2)
250    ///     .collect::<Vec<_>>();
251    /// assert_eq!(data, vec![Ok(2), Err("some error"), Ok(6)]);
252    /// ```
253    fn map_res<F, T, U, E>(self, f: F) -> iterator::map_res::MapRes<Self, F>
254    where
255        Self: Sized,
256        Self: Iterator<Item = Result<T, E>>,
257        F: FnMut(T) -> U;
258
259    /// Transforms the `Err` values in iterators of `Result<T, E>` using the given function `f`.
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// # use type_toppings::IteratorExt;
265    /// let data = [Ok(1), Err("unexpected thing happened"), Ok(3)]
266    ///     .into_iter()
267    ///     .map_res_err(|err| format!("Oh no: {err}"))
268    ///     .collect::<Vec<_>>();
269    /// assert_eq!(data, vec![Ok(1), Err("Oh no: unexpected thing happened".to_string()), Ok(3)]);
270    /// ```
271    fn map_res_err<F, T, U, E>(self, f: F) -> iterator::map_res_err::MapResErr<Self, F>
272    where
273        Self: Sized,
274        Self: Iterator<Item = Result<T, E>>,
275        F: FnMut(E) -> U;
276
277    /// Converts each element of the iterator to a string and joins them into a single string, separated by the specified separator.
278    ///
279    /// # Examples
280    ///
281    /// ```
282    /// # use type_toppings::IteratorExt as _;
283    /// let numbers = vec![1, 2, 3];
284    /// let sequence = numbers.into_iter().join_as_strings(", ");
285    /// assert_eq!(sequence, "1, 2, 3");
286    ///
287    /// let words = vec!["hello", "world"];
288    /// let sentence = words.into_iter().join_as_strings(" - ");
289    /// assert_eq!(sentence, "hello - world");
290    /// ```
291    fn join_as_strings(self, separator: &str) -> String
292    where
293        Self: Iterator,
294        <Self as Iterator>::Item: ToString;
295}