Skip to main content

toolu_orm_core/
journal.rs

1use 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  /// Reads a journal from a JSON file, returning an empty journal if the file does not exist.
63  ///
64  /// # Errors
65  ///
66  /// Returns [`DbCoreError::JournalRead`] if the file exists but cannot be read or parsed.
67  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  /// Writes the journal to a JSON file.
78  ///
79  /// # Errors
80  ///
81  /// Returns [`DbCoreError::JournalWrite`] if serialization or file writing fails.
82  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/// Computes a SHA-256 hash of the given content, prefixed with `sha256:`.
90#[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}