1pub use crate::error::SessionLogError;
2use crate::model::{SessionEvent, SessionMeta};
3use serde_json::Error as JsonError;
4use std::fs::File;
5use std::io::{BufRead, BufReader};
6use std::path::Path;
7
8#[derive(Debug, Clone)]
9pub struct SessionLine {
10 pub line_number: usize,
11 pub bytes_read: usize,
12 pub raw: String,
13}
14
15#[derive(Debug)]
16pub enum SessionLogEntry {
17 Persisted { line: SessionLine, event: Box<SessionEvent> },
18 Transient { line: SessionLine },
19 Malformed { line: SessionLine, error: JsonError },
20}
21
22impl SessionLogEntry {
23 pub fn parse(line: SessionLine) -> Self {
24 match serde_json::from_str::<SessionEvent>(&line.raw) {
25 Ok(event) if event.is_persisted() => Self::Persisted { line, event: Box::new(event) },
26 Ok(_) => Self::Transient { line },
27 Err(error) => Self::Malformed { line, error },
28 }
29 }
30
31 pub fn line(&self) -> &SessionLine {
32 match self {
33 Self::Persisted { line, .. } | Self::Transient { line } | Self::Malformed { line, .. } => line,
34 }
35 }
36}
37
38pub struct SessionLog<T: BufRead> {
39 reader: T,
40 pub meta: SessionMeta,
41 line_number: usize,
42 bytes_read: usize,
43 metadata_bytes: usize,
44}
45
46impl SessionLog<BufReader<File>> {
47 pub fn open(path: impl AsRef<Path>) -> Result<Self, SessionLogError> {
48 Self::from_reader(BufReader::new(File::open(path.as_ref())?))
49 }
50}
51
52impl<T: BufRead> SessionLog<T> {
53 pub fn from_reader(mut reader: T) -> Result<Self, SessionLogError> {
54 let mut line = String::new();
55 let mut line_number = 0;
56 loop {
57 line.clear();
58 if reader.read_line(&mut line)? == 0 {
59 return Err(SessionLogError::MissingMetadata);
60 }
61 line_number += 1;
62 if !line.trim().is_empty() {
63 break;
64 }
65 }
66 let meta = serde_json::from_str(line.trim())
67 .map_err(|source| SessionLogError::InvalidMetadata { line_number, source })?;
68 Ok(Self { reader, meta, line_number, bytes_read: line.len(), metadata_bytes: line.len() })
69 }
70
71 pub fn bytes_read(&self) -> usize {
72 self.bytes_read
73 }
74
75 pub fn meta_line_bytes(&self) -> usize {
76 self.metadata_bytes
77 }
78
79 pub fn next_entry(&mut self) -> std::io::Result<Option<SessionLogEntry>> {
80 let Some(line) = self.next_line()? else {
81 return Ok(None);
82 };
83 let entry = SessionLogEntry::parse(line);
84 Ok(Some(entry))
85 }
86
87 pub fn next_line(&mut self) -> std::io::Result<Option<SessionLine>> {
88 let mut line = String::new();
89 loop {
90 line.clear();
91 let bytes_read = self.reader.read_line(&mut line)?;
92 if bytes_read == 0 {
93 return Ok(None);
94 }
95 self.line_number += 1;
96 self.bytes_read = self.bytes_read.saturating_add(bytes_read);
97 let trimmed = line.trim();
98 if !trimmed.is_empty() {
99 return Ok(Some(SessionLine { line_number: self.line_number, bytes_read, raw: trimmed.to_string() }));
100 }
101 }
102 }
103}