winbrew_database\journal/
reader.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3use thiserror::Error;
4
5use super::JournalEntry;
6
7#[derive(Debug, Error)]
8pub enum JournalReadError {
9    #[error("failed to read journal at {path}")]
10    Read {
11        path: PathBuf,
12        #[source]
13        source: std::io::Error,
14    },
15
16    #[error("journal at {path} is incomplete")]
17    Incomplete { path: PathBuf },
18
19    #[error("journal at {path} is malformed on line {line}")]
20    MalformedLine {
21        path: PathBuf,
22        line: usize,
23        #[source]
24        source: serde_json::Error,
25    },
26
27    #[error("journal at {path} has trailing entries after Commit on line {line}")]
28    TrailingEntries { path: PathBuf, line: usize },
29}
30
31pub struct JournalReader;
32
33impl JournalReader {
34    pub fn read_committed(path: &Path) -> std::result::Result<Vec<JournalEntry>, JournalReadError> {
35        let contents = fs::read_to_string(path).map_err(|source| JournalReadError::Read {
36            path: path.to_path_buf(),
37            source,
38        })?;
39
40        let lines = contents
41            .lines()
42            .map(str::trim)
43            .filter(|line| !line.is_empty())
44            .collect::<Vec<_>>();
45
46        if lines.is_empty() {
47            return Err(JournalReadError::Incomplete {
48                path: path.to_path_buf(),
49            });
50        }
51
52        let mut entries = Vec::with_capacity(lines.len());
53        let mut commit_seen = false;
54
55        for (index, line) in lines.iter().enumerate() {
56            if commit_seen {
57                return Err(JournalReadError::TrailingEntries {
58                    path: path.to_path_buf(),
59                    line: index + 1,
60                });
61            }
62
63            let entry = serde_json::from_str::<JournalEntry>(line).map_err(|source| {
64                JournalReadError::MalformedLine {
65                    path: path.to_path_buf(),
66                    line: index + 1,
67                    source,
68                }
69            })?;
70
71            commit_seen |= matches!(entry, JournalEntry::Commit { .. });
72            entries.push(entry);
73        }
74
75        if !commit_seen {
76            return Err(JournalReadError::Incomplete {
77                path: path.to_path_buf(),
78            });
79        }
80
81        Ok(entries)
82    }
83}