toolu_orm_core/
journal.rs1use serde::{Deserialize, Serialize};
2use sha2::{Digest, Sha256};
3use std::path::Path;
4
5use crate::error::DbCoreError;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Journal {
9 pub version: u32,
10 pub entries: Vec<JournalEntry>,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct JournalEntry {
15 pub idx: u32,
16 pub name: String,
17 pub hash: String,
18 pub created_at: u64,
19}
20
21impl Journal {
22 #[must_use]
23 pub fn empty() -> Self {
24 Self {
25 version: 1,
26 entries: Vec::new(),
27 }
28 }
29
30 pub fn add_entry(&mut self, name: &str, hash: &str) {
31 let idx = u32::try_from(self.entries.len()).unwrap_or(u32::MAX);
32 let created_at = std::time::SystemTime::now()
33 .duration_since(std::time::UNIX_EPOCH)
34 .unwrap_or_default()
35 .as_secs();
36 self.entries.push(JournalEntry {
37 idx,
38 name: name.to_owned(),
39 hash: hash.to_owned(),
40 created_at,
41 });
42 }
43
44 #[must_use]
45 pub fn next_migration_number(&self) -> u32 {
46 self
47 .entries
48 .iter()
49 .filter_map(|e| e.name.split('_').next()?.parse::<u32>().ok())
50 .max()
51 .map_or(1, |n| n + 1)
52 }
53
54 #[must_use]
55 pub fn latest_snapshot_name_owned(&self) -> Option<String> {
56 self.entries.last().map(|e| {
57 let base = e.name.strip_suffix(".sql").unwrap_or(&e.name);
58 format!("{base}.snapshot.json")
59 })
60 }
61
62 pub fn read_from_path(path: &str) -> Result<Self, DbCoreError> {
68 let p = Path::new(path);
69 if !p.exists() {
70 return Ok(Self::empty());
71 }
72 let content =
73 std::fs::read_to_string(p).map_err(|e| DbCoreError::JournalRead(format!("{path}: {e}")))?;
74 serde_json::from_str(&content).map_err(|e| DbCoreError::JournalRead(format!("{path}: {e}")))
75 }
76
77 pub fn write_to_path(&self, path: &str) -> Result<(), DbCoreError> {
83 let json = serde_json::to_string_pretty(self)
84 .map_err(|e| DbCoreError::JournalWrite(format!("{path}: {e}")))?;
85 std::fs::write(path, json).map_err(|e| DbCoreError::JournalWrite(format!("{path}: {e}")))
86 }
87}
88
89#[must_use]
91pub fn compute_hash(content: &str) -> String {
92 let mut hasher = Sha256::new();
93 hasher.update(content.as_bytes());
94 let result = hasher.finalize();
95 format!("sha256:{result:x}")
96}