From 174a9b2d40ddbf88b39344a64bbcc47f2f4314b2 Mon Sep 17 00:00:00 2001 From: Matthijs Hofstra Date: Wed, 19 Oct 2016 01:20:51 +0200 Subject: [PATCH 1/2] Add trait for Iterator> tracing --- libimagerror/src/iter.rs | 244 +++++++++++++++++++++++++++++++++++++++ libimagerror/src/lib.rs | 1 + 2 files changed, 245 insertions(+) create mode 100644 libimagerror/src/iter.rs diff --git a/libimagerror/src/iter.rs b/libimagerror/src/iter.rs new file mode 100644 index 00000000..473d3262 --- /dev/null +++ b/libimagerror/src/iter.rs @@ -0,0 +1,244 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2016 the imag contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::error::Error; + +/// An iterator that maps `f` over the `Error` elements of `iter`, similar to `std::iter::Map`. +/// +/// This `struct` is created by the `on_err()` method on `TraceIterator`. See its +/// documentation for more information. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +#[derive(Clone)] +pub struct OnErr{ + iter: I, + f: F +} + +impl Iterator for OnErr where + I: Iterator>, + F: FnMut(&E) +{ + type Item = Result; + + #[inline] + fn next(&mut self) -> Option { + match self.iter.next() { + Some(Err(e)) => { + (self.f)(&e); + Some(Err(e)) + }, + other => other + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl ExactSizeIterator for OnErr where + I: ExactSizeIterator, + OnErr: Iterator +{ +} + +impl DoubleEndedIterator for OnErr where + I: DoubleEndedIterator>, + F: FnMut(&E) +{ + #[inline] + fn next_back(&mut self) -> Option { + match self.iter.next_back() { + Some(Err(e)) => { + (self.f)(&e); + Some(Err(e)) + }, + other => other + } + } +} + +/// An iterator that unwraps the `Ok` items of `iter`, while passing the `Err` items to its +/// closure `f`. +/// +/// This `struct` is created by the `unwrap_with()` method on `TraceIterator`. See its +/// documentation for more information. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +#[derive(Clone)] +pub struct UnwrapWith{ + iter: I, + f: F +} + +impl Iterator for UnwrapWith where + I: Iterator>, + F: FnMut(E) +{ + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + loop { + match self.iter.next() { + Some(Err(e)) => { + (self.f)(e); + }, + Some(Ok(item)) => return Some(item), + None => return None, + } + } + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + let (_, upper) = self.iter.size_hint(); + (0, upper) + } +} + +impl DoubleEndedIterator for UnwrapWith where + I: DoubleEndedIterator>, + F: FnMut(E) +{ + #[inline] + fn next_back(&mut self) -> Option { + loop { + match self.iter.next_back() { + Some(Err(e)) => { + (self.f)(e); + }, + Some(Ok(item)) => return Some(item), + None => return None, + } + } + } +} + +/// This trait provides methods that make it easier to work with iterators that yield a `Result`. +pub trait TraceIterator : Iterator> + Sized { + /// Creates an iterator that yields the item in each `Ok` item, while filtering out the `Err` + /// items. Each filtered `Err` will be trace-logged with [`::trace::trace_error`]. + /// + /// As with all iterators, the processing is lazy. If you do not use the result of this method, + /// nothing will be passed to `::trace::trace_error`, no matter how many `Err` items might + /// be present. + #[inline] + fn trace_unwrap(self) -> UnwrapWith where E: Error { + #[inline] + fn trace_error(err: E) { + ::trace::trace_error(&err); + } + + self.unwrap_with(trace_error) + } + + /// Takes a closure and creates an iterator that will call that closure for each `Err` element. + /// The resulting iterator will yield the exact same items as the original iterator. A close + /// analogue from the standard library would be `Iterator::inspect`. + /// + /// As with all iterators, the processing is lazy. The result of this method must be evaluated + /// for the closure to be called. + #[inline] + fn on_err(self, f: F) -> OnErr where F: FnMut(&E) { + OnErr { iter: self, f: f } + } + + /// Takes a closure and creates an iterator that will yield the items inside all `Ok` items + /// yielded by the original iterator. All `Err` items will be filtered out, and the contents + /// of each `Err` will be passed to the closure. + /// + /// As with all iterators, the processing is lazy. The result of this method must be evaluated + /// for the closure to be called. + #[inline] + fn unwrap_with(self, f: F) -> UnwrapWith + where F: FnMut(E) + { + UnwrapWith { iter: self, f: f } + } +} + +impl TraceIterator for I where + I: Iterator> +{} + +#[cfg(test)] +mod test { + use super::TraceIterator; + + #[derive(Copy, Clone, Eq, PartialEq, Debug)] + struct TestError(i32); + + #[test] + fn test_unwrap_with() { + let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))]; + let mut errs = vec![]; + + let oks = original + .into_iter() + .unwrap_with(|e|errs.push(e)) + .collect::>(); + + assert_eq!(&oks, &[1, 3]); + assert_eq!(&errs, &[TestError(2), TestError(4)]); + } + + #[test] + fn test_unwrap_with_backward() { + let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))]; + let mut errs = vec![]; + + let oks = original + .into_iter() + .rev() + .unwrap_with(|e|errs.push(e)) + .collect::>(); + + assert_eq!(&oks, &[3, 1]); + assert_eq!(&errs, &[TestError(4), TestError(2)]); + } + + #[test] + fn test_on_err() { + let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))]; + let mut errs = vec![]; + + let result = original + .into_iter() + .on_err(|e|errs.push(e.clone())) + .collect::>(); + + assert_eq!(&result, &[Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))]); + assert_eq!(&errs, &[TestError(2), TestError(4)]); + } + + #[test] + fn test_on_err_backward() { + let original = vec![Ok(1), Err(TestError(2)), Ok(3), Err(TestError(4))]; + let mut errs = vec![]; + + let result = original + .into_iter() + .rev() + .on_err(|e|errs.push(e.clone())) + .collect::>(); + + assert_eq!(&result, &[Err(TestError(4)), Ok(3), Err(TestError(2)), Ok(1)]); + assert_eq!(&errs, &[TestError(4), TestError(2)]); + } +} diff --git a/libimagerror/src/lib.rs b/libimagerror/src/lib.rs index 5046ba5a..bc4f865d 100644 --- a/libimagerror/src/lib.rs +++ b/libimagerror/src/lib.rs @@ -37,3 +37,4 @@ extern crate ansi_term; pub mod into; pub mod error_gen; pub mod trace; +pub mod iter; From 7a5f5b50610a34bd4812c1d6dda64caaa6fe0f09 Mon Sep 17 00:00:00 2001 From: Matthijs Hofstra Date: Fri, 21 Oct 2016 11:02:52 +0200 Subject: [PATCH 2/2] Simplify `OnErr` iterator `next` and `next_back` --- libimagerror/src/iter.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/libimagerror/src/iter.rs b/libimagerror/src/iter.rs index 473d3262..0297bc6a 100644 --- a/libimagerror/src/iter.rs +++ b/libimagerror/src/iter.rs @@ -38,13 +38,7 @@ impl Iterator for OnErr where #[inline] fn next(&mut self) -> Option { - match self.iter.next() { - Some(Err(e)) => { - (self.f)(&e); - Some(Err(e)) - }, - other => other - } + self.iter.next().map(|r| r.map_err(|e| { (self.f)(&e); e })) } #[inline] @@ -65,13 +59,7 @@ impl DoubleEndedIterator for OnErr where { #[inline] fn next_back(&mut self) -> Option { - match self.iter.next_back() { - Some(Err(e)) => { - (self.f)(&e); - Some(Err(e)) - }, - other => other - } + self.iter.next_back().map(|r| r.map_err(|e| { (self.f)(&e); e })) } }