use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::Path;
use std::str::FromStr;
const HISTORY_FILE_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryFile {
version: u32,
migrations: Vec<HistoryFileMigration>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryFileMigration {
pub id: u64,
pub name: String,
pub snapshot_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub checksum: Option<String>,
}
impl HistoryFile {
pub fn new() -> Self {
Self {
version: HISTORY_FILE_VERSION,
migrations: 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> {
if std::fs::exists(&path)? {
return Self::load(path);
}
Ok(Self::default())
}
pub fn migrations(&self) -> &[HistoryFileMigration] {
&self.migrations
}
pub fn next_migration_number(&self) -> u32 {
self.migrations
.last()
.and_then(|m| {
m.name.split('_').next()?.parse::<u32>().ok()
})
.map(|n| n + 1)
.unwrap_or(0)
}
pub fn add_migration(&mut self, migration: HistoryFileMigration) {
self.migrations.push(migration);
}
pub fn remove_migration(&mut self, index: usize) {
self.migrations.remove(index);
}
}
impl Default for HistoryFile {
fn default() -> Self {
Self::new()
}
}
impl FromStr for HistoryFile {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let file: HistoryFile = toml::from_str(s)?;
if file.version != HISTORY_FILE_VERSION {
bail!(
"Unsupported history file version: {}. Expected version {}",
file.version,
HISTORY_FILE_VERSION
);
}
Ok(file)
}
}
impl fmt::Display for HistoryFile {
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)
}
}