toolpath_gemini/
reader.rs1use crate::error::{ConvoError, Result};
4use crate::types::{ChatFile, LogEntry};
5use std::fs;
6use std::path::Path;
7
8pub struct ConversationReader;
9
10impl ConversationReader {
11 pub fn read_chat_file<P: AsRef<Path>>(path: P) -> Result<ChatFile> {
13 let path = path.as_ref();
14 if !path.exists() {
15 return Err(ConvoError::ConversationNotFound(path.display().to_string()));
16 }
17 let bytes = fs::read(path)?;
18 let chat: ChatFile = serde_json::from_slice(&bytes)
19 .map_err(|e| ConvoError::Other(anyhow::anyhow!("{}: {}", path.display(), e)))?;
20 Ok(chat)
21 }
22
23 pub fn file_size<P: AsRef<Path>>(path: P) -> Result<u64> {
25 let path = path.as_ref();
26 if !path.exists() {
27 return Err(ConvoError::ConversationNotFound(path.display().to_string()));
28 }
29 Ok(fs::metadata(path)?.len())
30 }
31
32 pub fn read_logs<P: AsRef<Path>>(path: P) -> Result<Vec<LogEntry>> {
37 let path = path.as_ref();
38 if !path.exists() {
39 return Ok(Vec::new());
40 }
41 let bytes = fs::read(path)?;
42 match serde_json::from_slice::<Vec<LogEntry>>(&bytes) {
43 Ok(v) => Ok(v),
44 Err(e) => {
45 eprintln!(
46 "Warning: Failed to parse Gemini log file {}: {}",
47 path.display(),
48 e
49 );
50 Ok(Vec::new())
51 }
52 }
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use std::io::Write;
60 use tempfile::NamedTempFile;
61
62 fn write_chat(body: &str) -> NamedTempFile {
63 let mut f = NamedTempFile::new().unwrap();
64 f.write_all(body.as_bytes()).unwrap();
65 f.flush().unwrap();
66 f
67 }
68
69 #[test]
70 fn test_read_chat_file() {
71 let f = write_chat(
72 r#"{"sessionId":"s","projectHash":"h","messages":[{"id":"m","timestamp":"t","type":"user","content":"hi"}]}"#,
73 );
74 let chat = ConversationReader::read_chat_file(f.path()).unwrap();
75 assert_eq!(chat.session_id, "s");
76 assert_eq!(chat.messages.len(), 1);
77 }
78
79 #[test]
80 fn test_read_chat_file_nonexistent() {
81 let err = ConversationReader::read_chat_file("/nonexistent.json").unwrap_err();
82 matches!(err, ConvoError::ConversationNotFound(_));
83 }
84
85 #[test]
86 fn test_read_chat_file_invalid_json() {
87 let f = write_chat("not json");
88 let err = ConversationReader::read_chat_file(f.path()).unwrap_err();
89 matches!(err, ConvoError::Other(_));
90 }
91
92 #[test]
93 fn test_read_logs() {
94 let f = write_chat(
95 r#"[{"sessionId":"s","messageId":0,"type":"user","message":"hi","timestamp":"t"}]"#,
96 );
97 let logs = ConversationReader::read_logs(f.path()).unwrap();
98 assert_eq!(logs.len(), 1);
99 assert_eq!(logs[0].message, "hi");
100 }
101
102 #[test]
103 fn test_read_logs_absent() {
104 let logs = ConversationReader::read_logs("/nonexistent.json").unwrap();
105 assert!(logs.is_empty());
106 }
107
108 #[test]
109 fn test_read_logs_malformed_returns_empty() {
110 let f = write_chat("{");
111 let logs = ConversationReader::read_logs(f.path()).unwrap();
112 assert!(logs.is_empty());
113 }
114
115 #[test]
116 fn test_file_size() {
117 let f = write_chat(r#"{"messages":[]}"#);
118 let size = ConversationReader::file_size(f.path()).unwrap();
119 assert!(size > 0);
120 }
121
122 #[test]
123 fn test_file_size_nonexistent() {
124 let err = ConversationReader::file_size("/nonexistent.json").unwrap_err();
125 matches!(err, ConvoError::ConversationNotFound(_));
126 }
127}