1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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())
    }
}