csv-diff 0.1.2

Compare two CSVs - with ludicrous speed 🚀.
Documentation
use csv::ByteRecord;
use std::cmp::max;

/// Holds both left and right CSV headers (if present).
/// The difference to [`Headers`] is that this holds headers that might __not__ have been parsed successfully.
#[derive(Debug, Default)]
pub struct HeadersParsed {
    pub(crate) headers_left: Option<csv::Result<ByteRecord>>,
    pub(crate) headers_right: Option<csv::Result<ByteRecord>>,
}

impl HeadersParsed {
    pub(crate) fn new(
        headers_left: Option<csv::Result<ByteRecord>>,
        headers_right: Option<csv::Result<ByteRecord>>,
    ) -> Self {
        Self {
            headers_left,
            headers_right,
        }
    }

    /// Return the [`Result`] of parsing headers of the left CSV.
    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
    /// this will return `None`.
    pub fn headers_left(&self) -> Option<Result<&ByteRecord, &csv::Error>> {
        Some(self.headers_left.as_ref()?.as_ref())
    }

    /// Return the [`Result`] of parsing headers of the right CSV.
    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
    /// this will return `None`.
    pub fn headers_right(&self) -> Option<Result<&ByteRecord, &csv::Error>> {
        Some(self.headers_right.as_ref()?.as_ref())
    }

    pub(crate) fn max_num_cols(&self) -> Option<usize> {
        max(
            self.headers_left()
                .and_then(|hl| hl.as_ref().map(|csv| csv.len()).ok()),
            self.headers_right()
                .and_then(|hr| hr.as_ref().map(|csv| csv.len()).ok()),
        )
    }
}

impl
    From<(
        Option<csv::Result<ByteRecord>>,
        Option<csv::Result<ByteRecord>>,
    )> for HeadersParsed
{
    fn from(
        (headers_left, headers_right): (
            Option<csv::Result<ByteRecord>>,
            Option<csv::Result<ByteRecord>>,
        ),
    ) -> Self {
        Self::new(headers_left, headers_right)
    }
}

/// Holds both left and right CSV headers (if present).
/// The difference to [`HeadersParsed`] is that this holds headers (if present) that have been parsed __successfully__.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct Headers {
    pub(crate) headers_left: Option<ByteRecord>,
    pub(crate) headers_right: Option<ByteRecord>,
}
impl Headers {
    pub(crate) fn new(headers_left: Option<ByteRecord>, headers_right: Option<ByteRecord>) -> Self {
        Self {
            headers_left,
            headers_right,
        }
    }

    /// Return the successfully parsed headers of the left CSV (if present).
    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
    /// this will return `None`.
    pub fn headers_left(&self) -> Option<&ByteRecord> {
        self.headers_left.as_ref()
    }

    /// Return the successfully parsed headers of the right CSV (if present).
    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
    /// this will return `None`.
    pub fn headers_right(&self) -> Option<&ByteRecord> {
        self.headers_right.as_ref()
    }

    pub(crate) fn max_num_cols(&self) -> Option<usize> {
        max(
            self.headers_left().map(|hl| hl.len()),
            self.headers_right().map(|hr| hr.len()),
        )
    }
}

impl From<(Option<ByteRecord>, Option<ByteRecord>)> for Headers {
    fn from((headers_left, headers_right): (Option<ByteRecord>, Option<ByteRecord>)) -> Self {
        Self::new(headers_left, headers_right)
    }
}

impl TryFrom<HeadersParsed> for Headers {
    type Error = csv::Error;

    fn try_from(value: HeadersParsed) -> Result<Self, Self::Error> {
        Ok((
            value.headers_left.transpose()?,
            value.headers_right.transpose()?,
        )
            .into())
    }
}