Skip to main content

aether_sessions/
log.rs

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}