looprs_core/adapters/
fs_session_store.rs1use std::fs::{self, OpenOptions};
4use std::io::Write;
5use std::path::{Path, PathBuf};
6
7use anyhow::Context as _;
8use chrono::Utc;
9use serde::Serialize;
10use uuid::Uuid;
11
12use crate::ports::session_store::{SessionEvent, SessionStore};
13
14pub struct FsSessionStore {
16 session_id: String,
17 path: PathBuf,
18}
19
20impl FsSessionStore {
21 pub fn new(sessions_dir: PathBuf) -> Result<Self, anyhow::Error> {
23 let session_id = format!("sess-{}", Uuid::new_v4());
24 let date = Utc::now().format("%Y-%m-%d");
25 let filename = format!("{}-{}.jsonl", date, session_id);
26 fs::create_dir_all(&sessions_dir).with_context(|| {
27 format!("failed to create sessions dir: {}", sessions_dir.display())
28 })?;
29 let path = sessions_dir.join(filename);
30 Ok(Self { session_id, path })
31 }
32}
33
34impl SessionStore for FsSessionStore {
35 fn log(&mut self, event: SessionEvent) -> Result<(), anyhow::Error> {
36 #[derive(Serialize)]
37 struct LogLine<'a> {
38 ts: String,
39 session_id: &'a str,
40 #[serde(flatten)]
41 event: &'a SessionEvent,
42 }
43 let line = LogLine {
44 ts: Utc::now().to_rfc3339(),
45 session_id: &self.session_id,
46 event: &event,
47 };
48 let mut json = serde_json::to_string(&line).context("failed to serialize log line")?;
49 json.push('\n');
50 let mut file = OpenOptions::new()
51 .create(true)
52 .append(true)
53 .open(&self.path)
54 .with_context(|| format!("failed to open log file: {}", self.path.display()))?;
55 file.write_all(json.as_bytes())
56 .context("failed to write log line")?;
57 Ok(())
58 }
59
60 fn path(&self) -> Option<&Path> {
61 Some(&self.path)
62 }
63
64 fn session_id(&self) -> &str {
65 &self.session_id
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn conformance() {
75 let dir = tempfile::tempdir().expect("failed to create tempdir");
76 let mut store =
77 FsSessionStore::new(dir.path().join("sessions")).expect("failed to create store");
78 crate::ports::test_contracts::assert_session_store_contract(&mut store);
79 }
80}