Skip to main content

yuzu_data/
source.rs

1use crate::error::DataError;
2use std::fs;
3use std::path::PathBuf;
4
5/// Read-only byte store. `Ok(None)` means the key is absent (fail-soft).
6pub trait ObjectSource {
7    fn get(&self, key: &str) -> Result<Option<Vec<u8>>, DataError>;
8}
9
10/// Files under a root directory; `key` is a relative path (e.g. "prices/AAPL.csv.gz").
11pub struct LocalSource {
12    root: PathBuf,
13}
14
15impl LocalSource {
16    pub fn new(root: impl Into<PathBuf>) -> Self {
17        LocalSource { root: root.into() }
18    }
19}
20
21impl ObjectSource for LocalSource {
22    fn get(&self, key: &str) -> Result<Option<Vec<u8>>, DataError> {
23        match fs::read(self.root.join(key)) {
24            Ok(bytes) => Ok(Some(bytes)),
25            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
26            Err(e) => Err(DataError::Io(e.to_string())),
27        }
28    }
29}
30
31/// Write-side counterpart to [`ObjectSource`]. Kept separate so the read path
32/// (and OSS consumers) stay write-free; only the panel rebuild needs this.
33pub trait ObjectSink {
34    fn put(&self, key: &str, bytes: &[u8]) -> Result<(), DataError>;
35}
36
37impl ObjectSink for LocalSource {
38    fn put(&self, key: &str, bytes: &[u8]) -> Result<(), DataError> {
39        let path = self.root.join(key);
40        if let Some(parent) = path.parent() {
41            fs::create_dir_all(parent).map_err(|e| DataError::Io(e.to_string()))?;
42        }
43        fs::write(path, bytes).map_err(|e| DataError::Io(e.to_string()))
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use std::fs;
51
52    #[test]
53    fn local_source_reads_present_and_missing() {
54        let dir = std::env::temp_dir().join("yuzu_data_source_test");
55        fs::create_dir_all(&dir).unwrap();
56        fs::write(dir.join("hello.bin"), b"hi").unwrap();
57        let src = LocalSource::new(&dir);
58        assert_eq!(src.get("hello.bin").unwrap(), Some(b"hi".to_vec()));
59        assert_eq!(src.get("nope.bin").unwrap(), None);
60    }
61
62    #[test]
63    fn local_source_put_writes_and_creates_parents() {
64        let dir = std::env::temp_dir().join("yuzu_data_sink_test");
65        let _ = fs::remove_dir_all(&dir);
66        fs::create_dir_all(&dir).unwrap();
67        let src = LocalSource::new(&dir);
68        // a key with a missing parent dir ("panels/") must still write
69        src.put("panels/close.csv.gz", b"data").unwrap();
70        assert_eq!(
71            src.get("panels/close.csv.gz").unwrap(),
72            Some(b"data".to_vec())
73        );
74    }
75}