winbrew_database\journal/
reader.rs1use 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}