observer_storage/
annotations.rs1use std::fs;
7use std::path::{Path, PathBuf};
8
9use serde_json::Value;
10
11pub fn annotations_path(dir: &Path) -> PathBuf {
13 dir.join("annotations.jsonl")
14}
15
16pub fn read_annotations(dir: &Path) -> Vec<Value> {
18 fs::read_to_string(annotations_path(dir))
19 .ok()
20 .and_then(|s| {
21 s.lines()
22 .filter(|l| !l.trim().is_empty())
23 .map(|l| serde_json::from_str(l).ok())
24 .collect()
25 })
26 .unwrap_or_default()
27}
28
29pub fn write_annotations(dir: &Path, annotations: &[Value]) -> Result<(), String> {
31 let path = annotations_path(dir);
32 let mut body = String::new();
33 for a in annotations {
34 body.push_str(&a.to_string());
35 body.push('\n');
36 }
37 fs::write(&path, body).map_err(|e| e.to_string())
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43 use serde_json::json;
44
45 #[test]
46 fn write_then_read_roundtrip() {
47 let dir = tempfile::tempdir().unwrap();
48 let annos = vec![
49 json!({ "id": "a1", "t": 100, "text": "first", "author": "local" }),
50 json!({ "id": "a2", "t": 500, "text": "second", "author": "local" }),
51 ];
52 write_annotations(dir.path(), &annos).unwrap();
53 let got = read_annotations(dir.path());
54 assert_eq!(got.len(), 2);
55 assert_eq!(got[0]["id"], "a1");
56 assert_eq!(got[1]["text"], "second");
57 }
58
59 #[test]
60 fn read_missing_file_is_empty() {
61 let dir = tempfile::tempdir().unwrap();
62 assert!(read_annotations(dir.path()).is_empty());
63 }
64
65 #[test]
66 fn overwrite_replaces_all() {
67 let dir = tempfile::tempdir().unwrap();
68 write_annotations(dir.path(), &[json!({ "id": "a1", "t": 0 })]).unwrap();
69 write_annotations(dir.path(), &[json!({ "id": "b1", "t": 9 })]).unwrap();
70 let got = read_annotations(dir.path());
71 assert_eq!(got.len(), 1);
72 assert_eq!(got[0]["id"], "b1");
73 }
74}