use crate::version::{JOURNAL_VERSION, snapshot_version};
use drizzle_types::Dialect;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Journal {
pub version: String,
pub dialect: Dialect,
pub entries: Vec<JournalEntry>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct JournalEntry {
pub idx: u32,
pub version: String,
pub when: u64,
pub tag: String,
pub breakpoints: bool,
}
impl Journal {
#[must_use]
pub fn new(dialect: Dialect) -> Self {
Self {
version: JOURNAL_VERSION.to_string(),
dialect,
entries: Vec::new(),
}
}
#[must_use]
pub fn next_idx(&self) -> u32 {
u32::try_from(self.entries.len()).unwrap_or(u32::MAX)
}
pub fn add_entry(&mut self, tag: String, breakpoints: bool) -> &JournalEntry {
self.entries.push(JournalEntry {
idx: self.next_idx(),
version: snapshot_version(self.dialect).to_string(),
when: current_timestamp_ms(),
tag,
breakpoints,
});
let last_idx = self.entries.len() - 1;
&self.entries[last_idx]
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn load(path: &std::path::Path) -> std::io::Result<Self> {
let contents = std::fs::read_to_string(path)?;
serde_json::from_str(&contents)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
}
pub fn load_or_create(path: &std::path::Path, dialect: Dialect) -> std::io::Result<Self> {
if path.exists() {
Self::load(path)
} else {
Ok(Self::new(dialect))
}
}
pub fn save(&self, path: &std::path::Path) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(self)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, json)
}
}
fn current_timestamp_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_sqlite_journal() {
let journal = Journal::new(Dialect::SQLite);
assert_eq!(journal.version, "7");
assert_eq!(journal.dialect, Dialect::SQLite);
assert!(journal.entries.is_empty());
}
#[test]
fn test_add_entry() {
let mut journal = Journal::new(Dialect::SQLite);
journal.add_entry("0000_initial".to_string(), true);
assert_eq!(journal.entries.len(), 1);
assert_eq!(journal.entries[0].idx, 0);
assert_eq!(journal.entries[0].tag, "0000_initial");
assert!(journal.entries[0].breakpoints);
}
#[test]
fn test_journal_serialization() {
let mut journal = Journal::new(Dialect::SQLite);
journal.add_entry("0000_test".to_string(), true);
let json = journal.to_json().unwrap();
let parsed = Journal::from_json(&json).unwrap();
assert_eq!(parsed.version, journal.version);
assert_eq!(parsed.dialect, journal.dialect);
assert_eq!(parsed.entries.len(), 1);
}
}