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}