ferridriver_bdd/
data_table.rs1use std::ops::{Deref, DerefMut};
4
5use rustc_hash::FxHashMap;
6
7#[derive(Debug, Clone)]
9pub struct DataTable {
10 rows: Vec<Vec<String>>,
11}
12
13impl DataTable {
14 pub fn new(rows: Vec<Vec<String>>) -> Self {
15 Self { rows }
16 }
17
18 pub fn raw(&self) -> &[Vec<String>] {
19 &self.rows
20 }
21
22 pub fn is_empty(&self) -> bool {
23 self.rows.is_empty()
24 }
25
26 pub fn len(&self) -> usize {
27 self.rows.len()
28 }
29
30 pub fn headers(&self) -> Option<&[String]> {
32 self.rows.first().map(|r| r.as_slice())
33 }
34
35 pub fn data_rows(&self) -> &[Vec<String>] {
37 if self.rows.len() > 1 { &self.rows[1..] } else { &[] }
38 }
39
40 pub fn hashes(&self) -> Vec<FxHashMap<&str, &str>> {
42 let Some(headers) = self.headers() else {
43 return Vec::new();
44 };
45 self
46 .data_rows()
47 .iter()
48 .map(|row| {
49 headers
50 .iter()
51 .zip(row.iter())
52 .map(|(h, v)| (h.as_str(), v.as_str()))
53 .collect()
54 })
55 .collect()
56 }
57
58 pub fn rows_hash(&self) -> FxHashMap<&str, &str> {
60 self
61 .rows
62 .iter()
63 .filter(|r| r.len() >= 2)
64 .map(|r| (r[0].as_str(), r[1].as_str()))
65 .collect()
66 }
67
68 pub fn transpose(&self) -> DataTable {
70 if self.rows.is_empty() {
71 return DataTable::new(Vec::new());
72 }
73 let max_cols = self.rows.iter().map(|r| r.len()).max().unwrap_or(0);
74 let mut transposed = vec![Vec::with_capacity(self.rows.len()); max_cols];
75 for row in &self.rows {
76 for (col_idx, cell) in row.iter().enumerate() {
77 transposed[col_idx].push(cell.clone());
78 }
79 }
80 DataTable::new(transposed)
81 }
82
83 pub fn cell(&self, row: usize, col: usize) -> Option<&str> {
85 self.rows.get(row).and_then(|r| r.get(col)).map(String::as_str)
86 }
87}
88
89impl Deref for DataTable {
90 type Target = [Vec<String>];
91
92 fn deref(&self) -> &[Vec<String>] {
93 &self.rows
94 }
95}
96
97impl DerefMut for DataTable {
98 fn deref_mut(&mut self) -> &mut [Vec<String>] {
99 &mut self.rows
100 }
101}
102
103impl From<Vec<Vec<String>>> for DataTable {
104 fn from(rows: Vec<Vec<String>>) -> Self {
105 Self::new(rows)
106 }
107}
108
109pub trait FromDataTable: Sized {
111 fn from_row(headers: &[String], row: &[String]) -> ferridriver::error::Result<Self>;
112}
113
114impl DataTable {
115 pub fn as_type<T: FromDataTable>(&self) -> ferridriver::error::Result<Vec<T>> {
117 let headers = self
118 .headers()
119 .ok_or_else(|| ferridriver::FerriError::invalid_argument("data-table", "table has no header row"))?;
120 self.data_rows().iter().map(|row| T::from_row(headers, row)).collect()
121 }
122}