apif_source_row/
memory.rs1use crate::SourceReader;
2use crate::SourceRow;
3use anyhow::Result;
4use apif_source_error::SourceError;
5use std::collections::HashMap;
6
7pub struct InMemorySource {
8 data: HashMap<String, SourceRow>,
9 key_column: String,
10 headers: Vec<String>,
11 row_count: usize,
12}
13
14impl InMemorySource {
15 pub fn load(reader: &mut dyn SourceReader, key_column: &str) -> Result<Self> {
16 let headers = reader.headers().to_vec();
17 let mut data = HashMap::new();
18 let mut row_count = 0;
19
20 while let Some(row) = reader.next_row()? {
21 row_count += 1;
22 let key = row
23 .get(key_column)
24 .ok_or_else(|| {
25 SourceError::ColumnNotFound(key_column.to_string(), "<memory>".into())
26 })?
27 .to_string();
28 data.insert(key, row);
29 }
30
31 Ok(Self {
32 data,
33 key_column: key_column.to_string(),
34 headers,
35 row_count,
36 })
37 }
38
39 pub fn lookup(&self, key: &str) -> Option<&SourceRow> {
40 self.data.get(key)
41 }
42
43 #[must_use]
44 pub fn contains(&self, key: &str) -> bool {
45 self.data.contains_key(key)
46 }
47
48 pub fn len(&self) -> usize {
49 self.data.len()
50 }
51
52 #[must_use]
53 pub fn is_empty(&self) -> bool {
54 self.data.is_empty()
55 }
56
57 pub fn headers(&self) -> &[String] {
58 &self.headers
59 }
60
61 pub fn key_column(&self) -> &str {
62 &self.key_column
63 }
64
65 pub fn row_count(&self) -> usize {
66 self.row_count
67 }
68
69 pub fn iter(&self) -> impl Iterator<Item = (&String, &SourceRow)> {
70 self.data.iter()
71 }
72
73 pub fn filter(&self, conditions: &[super::filter::FilterCondition]) -> Self {
75 use crate::filter::matches_all;
76 let mut filtered_data = HashMap::new();
77 for (key, row) in &self.data {
78 if matches_all(row, conditions) {
79 filtered_data.insert(key.clone(), row.clone());
80 }
81 }
82 let row_count = filtered_data.len();
83 Self {
84 data: filtered_data,
85 key_column: self.key_column.clone(),
86 headers: self.headers.clone(),
87 row_count,
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::CsvReader;
96 use std::io::{BufReader, Cursor};
97
98 struct CsvFixtures;
99
100 impl CsvFixtures {
101 fn make_reader(data: &'static str) -> CsvReader<Cursor<&'static str>> {
102 CsvReader::new(BufReader::new(Cursor::new(data)), b',').unwrap()
103 }
104 }
105
106 #[test]
107 fn load_and_lookup() {
108 let data = "id,name,age\n1,Alice,30\n2,Bob,25\n3,Charlie,35\n";
109 let mut reader = CsvFixtures::make_reader(data);
110 let mem = InMemorySource::load(&mut reader, "id").unwrap();
111
112 assert_eq!(mem.len(), 3);
113 assert_eq!(mem.row_count(), 3);
114 assert_eq!(mem.headers(), &["id", "name", "age"]);
115
116 let row = mem.lookup("2").unwrap();
117 assert_eq!(row.get("name"), Some("Bob"));
118 assert_eq!(row.get("age"), Some("25"));
119 }
120
121 #[test]
122 fn lookup_missing_returns_none() {
123 let data = "id,val\n1,hello\n";
124 let mut reader = CsvFixtures::make_reader(data);
125 let mem = InMemorySource::load(&mut reader, "id").unwrap();
126 assert!(mem.lookup("999").is_none());
127 }
128
129 #[test]
130 fn contains_check() {
131 let data = "id,val\n1,hello\n2,world\n";
132 let mut reader = CsvFixtures::make_reader(data);
133 let mem = InMemorySource::load(&mut reader, "id").unwrap();
134 assert!(mem.contains("1"));
135 assert!(!mem.contains("3"));
136 }
137
138 #[test]
139 fn missing_key_column_errors() {
140 let data = "id,val\n1,hello\n";
141 let mut reader = CsvFixtures::make_reader(data);
142 let result = InMemorySource::load(&mut reader, "missing_col");
143 assert!(result.is_err());
144 }
145
146 #[test]
147 fn duplicate_keys_last_wins() {
148 let data = "id,val\n1,first\n1,second\n";
149 let mut reader = CsvFixtures::make_reader(data);
150 let mem = InMemorySource::load(&mut reader, "id").unwrap();
151 assert_eq!(mem.len(), 1);
152 assert_eq!(mem.row_count(), 2);
153 assert_eq!(mem.lookup("1").unwrap().get("val"), Some("second"));
154 }
155
156 #[test]
157 fn empty_source() {
158 let data = "id,val\n";
159 let mut reader = CsvFixtures::make_reader(data);
160 let mem = InMemorySource::load(&mut reader, "id").unwrap();
161 assert!(mem.is_empty());
162 assert_eq!(mem.row_count(), 0);
163 }
164
165 #[test]
166 fn iter_all_rows() {
167 let data = "id,val\n1,a\n2,b\n";
168 let mut reader = CsvFixtures::make_reader(data);
169 let mem = InMemorySource::load(&mut reader, "id").unwrap();
170 let keys: Vec<&str> = mem.iter().map(|(k, _)| k.as_str()).collect();
171 assert!(keys.contains(&"1"));
172 assert!(keys.contains(&"2"));
173 }
174}