Skip to main content

observer_storage/
annotations.rs

1//! 会话级标注存储:annotations.jsonl,每行一条标注 JSON。
2//!
3//! 标注是回放侧「人对会话的批注」,与 segment 事件流分离,保持原始录制数据纯净。
4//! 整体覆盖写入([`write_annotations`]):前端持有完整列表,增删改后整体覆写。
5
6use std::fs;
7use std::path::{Path, PathBuf};
8
9use serde_json::Value;
10
11/// 会话目录下的标注文件路径。
12pub fn annotations_path(dir: &Path) -> PathBuf {
13    dir.join("annotations.jsonl")
14}
15
16/// 读全部标注(按文件行序)。无文件返回空数组。
17pub 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
29/// 整体覆写标注文件(每行一条 JSON)。
30pub 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}