csvsc 2.2.1

Build processing chains for CSV files
Documentation
use crate::{Headers, Row, RowStream, RowResult, error};

pub struct MapRow<I, F> {
    iter: I,
    f: F,
    old_headers: Headers,
    headers: Headers,
}

/// Allows you to build a custom transformation of the current row
impl<I, F, R> MapRow<I, F>
where
    I: RowStream,
    F: Fn(&Headers, &Row) -> error::Result<R>,
    R: Iterator<Item = RowResult>,
{
    pub fn new(
        iter: I,
        f: F,
        headers: Headers,
    ) -> MapRow<I, F> {
        let old_headers = iter.headers().clone();

        MapRow {
            iter,
            f,
            old_headers,
            headers,
        }
    }
}

pub struct IntoIter<I, F, R>
where
    I: Iterator<Item = RowResult>,
    F: Fn(&Headers, &Row) -> error::Result<R>,
    R: Iterator<Item = RowResult>,
{
    iter: I,
    f: F,
    old_headers: Headers,
    cur_item: Option<R>,
}

impl<I, F, R> Iterator for IntoIter<I, F, R>
where
    I: Iterator<Item = RowResult>,
    F: Fn(&Headers, &Row) -> error::Result<R>,
    R: Iterator<Item = RowResult>,
{
    type Item = RowResult;

    fn next(&mut self) -> Option<Self::Item> {
        match self.cur_item.as_mut() {
            Some(iter) => match iter.next() {
                Some(res) => Some(res),
                None => {
                    // the current chunk ran out of rows, go for the next one
                    self.cur_item = None;

                    self.next()
                },
            },
            None => match self.iter.next() {
                // compute the next chunk based on the next available row
                Some(row) => match row {
                    Ok(row) => match (self.f)(&self.old_headers, &row) {
                        Ok(stream) => {
                            self.cur_item = Some(stream);

                            self.next()
                        },
                        Err(e) => {
                            Some(Err(e))
                        },
                    }
                    Err(e) => Some(Err(e)),
                },
                None => None,
            },
        }
    }
}

impl<I, F, R> IntoIterator for MapRow<I, F>
where
    I: RowStream,
    F: Fn(&Headers, &Row) -> error::Result<R>,
    R: Iterator<Item = RowResult>,
{
    type Item = RowResult;

    type IntoIter = IntoIter<I::IntoIter, F, R>;

    fn into_iter(self) -> Self::IntoIter {
        Self::IntoIter {
            iter: self.iter.into_iter(),
            f: self.f,
            old_headers: self.old_headers,
            cur_item: None,
        }
    }
}

impl<I, F, R> RowStream for MapRow<I, F>
where
    I: RowStream,
    F: Fn(&Headers, &Row) -> error::Result<R>,
    R: Iterator<Item = RowResult>,
{
    fn headers(&self) -> &Headers {
        &self.headers
    }
}

#[cfg(test)]
mod tests {
    use super::MapRow;

    use crate::{Row, mock::MockStream, error, RowStream};

    #[test]
    fn test_row_transpose() {
        let iter = MockStream::from_rows(
            vec![
                Ok(Row::from(vec!["a1", "a2"])),
                Ok(Row::from(vec!["1", "2"])),
                Err(error::Error::InconsistentHeaders),
                Ok(Row::from(vec!["3", "4"])),
            ].into_iter(),
        )
        .unwrap();

        let map_row = MapRow::new(iter, |headers, row| {
            Ok(vec![
                Ok(Row::from(vec![headers.get_field(row, "a1").unwrap()])),
                Ok(Row::from(vec![headers.get_field(row, "a2").unwrap()])),
            ].into_iter())
        }, Row::from(vec!["a"]).into());

        assert_eq!(map_row.headers(), &Row::from(vec!["a"]).into());

        let mut r = map_row.into_iter();

        assert_eq!(r.next().unwrap().unwrap(), Row::from(vec!["1"]));
        assert_eq!(r.next().unwrap().unwrap(), Row::from(vec!["2"]));
        assert_eq!(r.next().unwrap().unwrap_err(), error::Error::InconsistentHeaders);
        assert_eq!(r.next().unwrap().unwrap(), Row::from(vec!["3"]));
        assert_eq!(r.next().unwrap().unwrap(), Row::from(vec!["4"]));
        assert!(r.next().is_none());
    }
}