Skip to main content

apif_source_row/
memory.rs

1use 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    /// Filter the in-memory source, keeping only rows that match ALL filter conditions.
74    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}