pawan/config/
migration.rs1use super::defaults::default_tool_idle_timeout;
7use super::PawanConfig;
8use chrono::Utc;
9use std::path::PathBuf;
10use tracing;
11
12const LATEST_CONFIG_VERSION: u32 = 1;
14
15#[derive(Debug)]
17pub struct MigrationResult {
18 pub migrated: bool,
20 pub from_version: u32,
22 pub to_version: u32,
24 pub backup_path: Option<PathBuf>,
26}
27
28impl MigrationResult {
29 pub fn new(from_version: u32, to_version: u32, backup_path: Option<PathBuf>) -> Self {
30 Self {
31 migrated: from_version != to_version,
32 from_version,
33 to_version,
34 backup_path,
35 }
36 }
37
38 pub fn no_migration(version: u32) -> Self {
39 Self {
40 migrated: false,
41 from_version: version,
42 to_version: version,
43 backup_path: None,
44 }
45 }
46}
47
48pub fn migrate_to_latest(
55 config: &mut PawanConfig,
56 config_path: Option<&PathBuf>,
57) -> MigrationResult {
58 let current_version = config.config_version;
59
60 if current_version >= LATEST_CONFIG_VERSION {
61 return MigrationResult::no_migration(current_version);
62 }
63
64 let backup_path = config_path.and_then(|path| create_backup(path).ok());
65
66 let mut version = current_version;
67 while version < LATEST_CONFIG_VERSION {
68 version = match apply_migration(config, version + 1) {
69 Ok(v) => v,
70 Err(e) => {
71 tracing::error!(
72 from_version = version,
73 to_version = LATEST_CONFIG_VERSION,
74 error = %e,
75 "Config migration failed"
76 );
77 return MigrationResult::new(current_version, version, backup_path);
78 }
79 };
80 }
81
82 config.config_version = LATEST_CONFIG_VERSION;
83 MigrationResult::new(current_version, LATEST_CONFIG_VERSION, backup_path)
84}
85
86pub fn save_config(config: &PawanConfig, path: &PathBuf) -> Result<(), String> {
88 let toml_string = toml::to_string_pretty(config)
89 .map_err(|e| format!("Failed to serialize config to TOML: {}", e))?;
90
91 std::fs::write(path, toml_string)
92 .map_err(|e| format!("Failed to write config to {}: {}", path.display(), e))?;
93
94 tracing::info!(path = %path.display(), "Config saved");
95 Ok(())
96}
97
98fn apply_migration(config: &mut PawanConfig, target_version: u32) -> Result<u32, String> {
103 match target_version {
104 1 => migrate_to_v1(config),
105 _ => Err(format!("Unknown target version: {}", target_version)),
106 }
107}
108
109pub(super) fn migrate_to_v1(config: &mut PawanConfig) -> Result<u32, String> {
112 config.config_version = 1;
113 if config.tool_call_idle_timeout_secs == 0 {
114 config.tool_call_idle_timeout_secs = default_tool_idle_timeout();
115 }
116 tracing::info!("Config migrated to version 1");
117 Ok(1)
118}
119
120fn create_backup(config_path: &PathBuf) -> Result<PathBuf, String> {
121 let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
122 let backup_path = config_path.with_extension(format!("toml.backup.{}", timestamp));
123
124 std::fs::copy(config_path, &backup_path).map_err(|e| {
125 format!(
126 "Failed to create backup at {}: {}",
127 backup_path.display(),
128 e
129 )
130 })?;
131
132 tracing::info!(backup = %backup_path.display(), "Config backup created");
133 Ok(backup_path)
134}