1use crate::error::CsvError;
4
5#[derive(Debug, Clone)]
10pub struct Row {
11 record: csv::StringRecord,
12}
13
14impl Row {
15 #[must_use]
17 pub(crate) fn from_record(record: csv::StringRecord) -> Self {
18 Self { record }
19 }
20
21 pub fn get(&self, index: usize) -> Result<&str, CsvError> {
27 self.record.get(index)
28 .ok_or(CsvError::MissingField { index })
29 }
30
31 #[must_use]
33 pub fn len(&self) -> usize {
34 self.record.len()
35 }
36
37 #[must_use]
39 pub fn is_empty(&self) -> bool {
40 self.record.is_empty()
41 }
42
43 pub fn fields(&self) -> impl Iterator<Item = &str> {
45 self.record.iter()
46 }
47
48 pub fn deserialize<'de, T: serde::Deserialize<'de>>(
58 &'de self,
59 headers: Option<&'de csv::StringRecord>,
60 ) -> Result<T, CsvError> {
61 self.record.deserialize(headers)
62 .map_err(|e| CsvError::Deserialize(e.to_string()))
63 }
64
65 #[must_use]
67 pub fn to_vec(&self) -> Vec<String> {
68 self.record.iter().map(String::from).collect()
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 fn sample_row() -> Row {
77 let record = csv::StringRecord::from(vec!["alice", "30", "seattle"]);
78 Row::from_record(record)
79 }
80
81 #[test]
82 fn get_returns_field_by_index() -> Result<(), CsvError> {
83 let row = sample_row();
84 assert_eq!(row.get(0)?, "alice");
85 assert_eq!(row.get(1)?, "30");
86 assert_eq!(row.get(2)?, "seattle");
87 Ok(())
88 }
89
90 #[test]
91 fn get_out_of_bounds_returns_error() {
92 let row = sample_row();
93 assert!(row.get(99).is_err());
94 }
95
96 #[test]
97 fn len_and_is_empty() {
98 let row = sample_row();
99 assert_eq!(row.len(), 3);
100 assert!(!row.is_empty());
101
102 let empty = Row::from_record(csv::StringRecord::new());
103 assert!(empty.is_empty());
104 }
105
106 #[test]
107 fn to_vec_collects_all_fields() {
108 let row = sample_row();
109 assert_eq!(row.to_vec(), vec!["alice", "30", "seattle"]);
110 }
111}