use chrono::Utc;
use tracing;
use std::path::PathBuf;
use super::{PawanConfig, default_tool_idle_timeout};
const LATEST_CONFIG_VERSION: u32 = 1;
#[derive(Debug)]
pub struct MigrationResult {
pub migrated: bool,
pub from_version: u32,
pub to_version: u32,
pub backup_path: Option<PathBuf>,
}
impl MigrationResult {
pub fn new(from_version: u32, to_version: u32, backup_path: Option<PathBuf>) -> Self {
Self {
migrated: from_version != to_version,
from_version,
to_version,
backup_path,
}
}
pub fn no_migration(version: u32) -> Self {
Self {
migrated: false,
from_version: version,
to_version: version,
backup_path: None,
}
}
}
pub fn migrate_to_latest(config: &mut PawanConfig, config_path: Option<&PathBuf>) -> MigrationResult {
let current_version = config.config_version;
if current_version >= LATEST_CONFIG_VERSION {
return MigrationResult::no_migration(current_version);
}
let backup_path = config_path.and_then(|path| create_backup(path).ok());
let mut version = current_version;
while version < LATEST_CONFIG_VERSION {
version = match apply_migration(config, version + 1) {
Ok(v) => v,
Err(e) => {
tracing::error!(
from_version = version,
to_version = LATEST_CONFIG_VERSION,
error = %e,
"Config migration failed"
);
return MigrationResult::new(current_version, version, backup_path);
}
};
}
config.config_version = LATEST_CONFIG_VERSION;
MigrationResult::new(current_version, LATEST_CONFIG_VERSION, backup_path)
}
pub fn save_config(config: &PawanConfig, path: &PathBuf) -> Result<(), String> {
let toml_string = toml::to_string_pretty(config)
.map_err(|e| format!("Failed to serialize config to TOML: {}", e))?;
std::fs::write(path, toml_string)
.map_err(|e| format!("Failed to write config to {}: {}", path.display(), e))?;
tracing::info!(path = %path.display(), "Config saved");
Ok(())
}
fn apply_migration(config: &mut PawanConfig, target_version: u32) -> Result<u32, String> {
match target_version {
1 => migrate_to_v1(config),
_ => Err(format!("Unknown target version: {}", target_version)),
}
}
pub(super) fn migrate_to_v1(config: &mut PawanConfig) -> Result<u32, String> {
config.config_version = 1;
if config.tool_call_idle_timeout_secs == 0 {
config.tool_call_idle_timeout_secs = default_tool_idle_timeout();
}
tracing::info!("Config migrated to version 1");
Ok(1)
}
fn create_backup(config_path: &PathBuf) -> Result<PathBuf, String> {
let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
let backup_path = config_path.with_extension(format!("toml.backup.{}", timestamp));
std::fs::copy(config_path, &backup_path)
.map_err(|e| format!("Failed to create backup at {}: {}", backup_path.display(), e))?;
tracing::info!(backup = %backup_path.display(), "Config backup created");
Ok(backup_path)
}