1#![expect(deprecated, reason = "We use `Context` to maintain compatibility")]
2
3use crate::{Context, Report};
4
5struct ReportShunt<'a, I, T, C> {
8 iter: I,
9
10 report: &'a mut Option<Report<[C]>>,
11 context_len: usize,
12 context_bound: usize,
13
14 _marker: core::marker::PhantomData<fn() -> *const T>,
15}
16
17impl<I, T, R, C> Iterator for ReportShunt<'_, I, T, C>
18where
19 I: Iterator<Item = Result<T, R>>,
20 R: Into<Report<[C]>>,
21{
22 type Item = T;
23
24 fn next(&mut self) -> Option<Self::Item> {
25 loop {
26 if self.context_len >= self.context_bound {
27 return None;
28 }
29
30 let item = self.iter.next()?;
31 let item = item.map_err(Into::into);
32
33 match (item, self.report.as_mut()) {
34 (Ok(output), None) => return Some(output),
35 (Ok(_), Some(_)) => {
36 }
39 (Err(error), None) => {
40 *self.report = Some(error);
41 self.context_len += 1;
42 }
43 (Err(error), Some(report)) => {
44 report.append(error);
45 self.context_len += 1;
46 }
47 }
48 }
49 }
50
51 fn size_hint(&self) -> (usize, Option<usize>) {
52 if self.report.is_some() {
53 (0, Some(0))
54 } else {
55 let (_, upper) = self.iter.size_hint();
56
57 (0, upper)
58 }
59 }
60}
61
62fn try_process_reports<I, T, R, C, F, U>(
63 iter: I,
64 bound: Option<usize>,
65 mut collect: F,
66) -> Result<U, Report<[C]>>
67where
68 I: Iterator<Item = Result<T, R>>,
69 R: Into<Report<[C]>>,
70 for<'a> F: FnMut(ReportShunt<'a, I, T, C>) -> U,
71{
72 let mut report = None;
73 let shunt = ReportShunt {
74 iter,
75 report: &mut report,
76 context_len: 0,
77 context_bound: bound.unwrap_or(usize::MAX),
78 _marker: core::marker::PhantomData,
79 };
80
81 let value = collect(shunt);
82 report.map_or_else(|| Ok(value), |report| Err(report))
83}
84
85pub trait TryReportIteratorExt<C> {
102 type Ok;
104
105 fn try_collect_reports<A>(self) -> Result<A, Report<[C]>>
137 where
138 A: FromIterator<Self::Ok>;
139
140 fn try_collect_reports_bounded<A>(self, bound: usize) -> Result<A, Report<[C]>>
174 where
175 A: FromIterator<Self::Ok>;
176}
177
178impl<T, C, R, I> TryReportIteratorExt<C> for I
179where
180 I: Iterator<Item = Result<T, R>>,
181 R: Into<Report<[C]>>,
182 C: Context,
183{
184 type Ok = T;
185
186 fn try_collect_reports<A>(self) -> Result<A, Report<[C]>>
187 where
188 A: FromIterator<Self::Ok>,
189 {
190 try_process_reports(self, None, |shunt| shunt.collect())
191 }
192
193 fn try_collect_reports_bounded<A>(self, bound: usize) -> Result<A, Report<[C]>>
194 where
195 A: FromIterator<Self::Ok>,
196 {
197 try_process_reports(self, Some(bound), |shunt| shunt.collect())
198 }
199}
200#[cfg(test)]
201mod tests {
202 #![allow(clippy::integer_division_remainder_used)]
203 use alloc::{collections::BTreeSet, vec::Vec};
204 use core::fmt;
205
206 use super::*;
207
208 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
209 struct CustomError(usize);
210
211 impl fmt::Display for CustomError {
212 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
213 write!(fmt, "CustomError({})", self.0)
214 }
215 }
216
217 impl core::error::Error for CustomError {}
218
219 #[test]
220 fn try_collect_multiple_errors() {
221 let iter = (0..5).map(|i| {
222 if i % 2 == 0 {
223 Ok(i)
224 } else {
225 Err(Report::new(CustomError(i)))
226 }
227 });
228
229 let result: Result<Vec<_>, Report<[CustomError]>> = iter.try_collect_reports();
230 let report = result.expect_err("should have failed");
231
232 let contexts: BTreeSet<_> = report.current_contexts().collect();
233 assert_eq!(contexts.len(), 2);
234 assert!(contexts.contains(&CustomError(1)));
235 assert!(contexts.contains(&CustomError(3)));
236 }
237
238 #[test]
239 fn try_collect_multiple_errors_bounded() {
240 let iter = (0..10).map(|i| {
241 if i % 2 == 0 {
242 Ok(i)
243 } else {
244 Err(Report::new(CustomError(i)))
245 }
246 });
247
248 let result: Result<Vec<_>, Report<[CustomError]>> = iter.try_collect_reports_bounded(3);
249 let report = result.expect_err("should have failed");
250
251 let contexts: BTreeSet<_> = report.current_contexts().collect();
252 assert_eq!(contexts.len(), 3);
253 assert!(contexts.contains(&CustomError(1)));
254 assert!(contexts.contains(&CustomError(3)));
255 assert!(contexts.contains(&CustomError(5)));
256 }
257
258 #[test]
259 fn try_collect_no_errors() {
260 let iter = (0..5).map(Result::<_, Report<CustomError>>::Ok);
261
262 let result: Result<Vec<_>, Report<[CustomError]>> = iter.try_collect_reports();
263 let values = result.expect("should have succeeded");
264
265 assert_eq!(values, [0, 1, 2, 3, 4]);
266 }
267
268 #[test]
269 fn try_collect_multiple_errors_expanded() {
270 let iter = (0..5).map(|i| {
271 if i % 2 == 0 {
272 Ok(i)
273 } else {
274 Err(Report::new(CustomError(i)).expand())
275 }
276 });
277
278 let result: Result<Vec<_>, Report<[CustomError]>> = iter.try_collect_reports();
279 let report = result.expect_err("should have failed");
280
281 let contexts: BTreeSet<_> = report.current_contexts().collect();
282 assert_eq!(contexts.len(), 2);
283 assert!(contexts.contains(&CustomError(1)));
284 assert!(contexts.contains(&CustomError(3)));
285 }
286}