use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::Path;
use std::str::FromStr;
const HISTORY_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct History {
version: u32,
#[serde(rename = "migrations")]
entries: Vec<HistoryEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryEntry {
pub id: u64,
pub name: String,
pub snapshot_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub checksum: Option<String>,
}
impl History {
pub fn new() -> Self {
Self {
version: HISTORY_VERSION,
entries: Vec::new(),
}
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let contents = std::fs::read_to_string(path.as_ref())?;
contents.parse()
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
std::fs::write(path.as_ref(), self.to_string())?;
Ok(())
}
pub fn load_or_default(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
if std::fs::exists(path)? {
return Self::load(path);
}
Ok(Self::default())
}
pub fn entries(&self) -> &[HistoryEntry] {
&self.entries
}
pub fn next_migration_number(&self) -> u32 {
self.entries
.last()
.and_then(|m| m.name.split('_').next()?.parse::<u32>().ok())
.map(|n| n + 1)
.unwrap_or(0)
}
pub fn add_entry(&mut self, entry: HistoryEntry) {
self.entries.push(entry);
}
pub fn remove_entry(&mut self, index: usize) {
self.entries.remove(index);
}
}
impl Default for History {
fn default() -> Self {
Self::new()
}
}
impl FromStr for History {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let history: History =
toml::from_str(s).map_err(|err| Error::from_args(format_args!("{err}")))?;
if history.version != HISTORY_VERSION {
return Err(Error::from_args(format_args!(
"unsupported history file version: {}. Expected version {}",
history.version, HISTORY_VERSION
)));
}
Ok(history)
}
}
impl fmt::Display for History {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let toml_str = toml::to_string_pretty(self).map_err(|_| fmt::Error)?;
write!(f, "{}", toml_str)
}
}