csv_diff/
csv_headers.rs

1use csv::ByteRecord;
2use std::cmp::max;
3
4/// Holds both left and right CSV headers (if present).
5/// The difference to [`Headers`] is that this holds headers that might __not__ have been parsed successfully.
6#[derive(Debug, Default)]
7pub struct HeadersParsed {
8    pub(crate) headers_left: Option<csv::Result<ByteRecord>>,
9    pub(crate) headers_right: Option<csv::Result<ByteRecord>>,
10}
11
12impl HeadersParsed {
13    pub(crate) fn new(
14        headers_left: Option<csv::Result<ByteRecord>>,
15        headers_right: Option<csv::Result<ByteRecord>>,
16    ) -> Self {
17        Self {
18            headers_left,
19            headers_right,
20        }
21    }
22
23    /// Return the [`Result`] of parsing headers of the left CSV.
24    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
25    /// this will return `None`.
26    pub fn headers_left(&self) -> Option<Result<&ByteRecord, &csv::Error>> {
27        Some(self.headers_left.as_ref()?.as_ref())
28    }
29
30    /// Return the [`Result`] of parsing headers of the right CSV.
31    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
32    /// this will return `None`.
33    pub fn headers_right(&self) -> Option<Result<&ByteRecord, &csv::Error>> {
34        Some(self.headers_right.as_ref()?.as_ref())
35    }
36
37    pub(crate) fn max_num_cols(&self) -> Option<usize> {
38        max(
39            self.headers_left()
40                .and_then(|hl| hl.as_ref().map(|csv| csv.len()).ok()),
41            self.headers_right()
42                .and_then(|hr| hr.as_ref().map(|csv| csv.len()).ok()),
43        )
44    }
45}
46
47impl
48    From<(
49        Option<csv::Result<ByteRecord>>,
50        Option<csv::Result<ByteRecord>>,
51    )> for HeadersParsed
52{
53    fn from(
54        (headers_left, headers_right): (
55            Option<csv::Result<ByteRecord>>,
56            Option<csv::Result<ByteRecord>>,
57        ),
58    ) -> Self {
59        Self::new(headers_left, headers_right)
60    }
61}
62
63/// Holds both left and right CSV headers (if present).
64/// The difference to [`HeadersParsed`] is that this holds headers (if present) that have been parsed __successfully__.
65#[derive(Debug, Default, PartialEq, Clone)]
66pub struct Headers {
67    pub(crate) headers_left: Option<ByteRecord>,
68    pub(crate) headers_right: Option<ByteRecord>,
69}
70impl Headers {
71    pub(crate) fn new(headers_left: Option<ByteRecord>, headers_right: Option<ByteRecord>) -> Self {
72        Self {
73            headers_left,
74            headers_right,
75        }
76    }
77
78    /// Return the successfully parsed headers of the left CSV (if present).
79    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
80    /// this will return `None`.
81    pub fn headers_left(&self) -> Option<&ByteRecord> {
82        self.headers_left.as_ref()
83    }
84
85    /// Return the successfully parsed headers of the right CSV (if present).
86    /// If the CSV has been parsed with [`has_headers(false)`](https://docs.rs/csv/1.3.0/csv/struct.ReaderBuilder.html#method.has_headers),
87    /// this will return `None`.
88    pub fn headers_right(&self) -> Option<&ByteRecord> {
89        self.headers_right.as_ref()
90    }
91
92    pub(crate) fn max_num_cols(&self) -> Option<usize> {
93        max(
94            self.headers_left().map(|hl| hl.len()),
95            self.headers_right().map(|hr| hr.len()),
96        )
97    }
98}
99
100impl From<(Option<ByteRecord>, Option<ByteRecord>)> for Headers {
101    fn from((headers_left, headers_right): (Option<ByteRecord>, Option<ByteRecord>)) -> Self {
102        Self::new(headers_left, headers_right)
103    }
104}
105
106impl TryFrom<HeadersParsed> for Headers {
107    type Error = csv::Error;
108
109    fn try_from(value: HeadersParsed) -> Result<Self, Self::Error> {
110        Ok((
111            value.headers_left.transpose()?,
112            value.headers_right.transpose()?,
113        )
114            .into())
115    }
116}