use crate::config_model::ServerConfig;
use serde_json::Value;
use anyhow::{Context, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigVersion {
V0,
V1,
}
fn detect_version(raw: &Value) -> Result<ConfigVersion> {
match raw.get("schema_version") {
None => {
log::info!("Config has no schema_version field, treating as v0 (legacy)");
Ok(ConfigVersion::V0)
}
Some(v) => match v.as_u64() {
Some(1) => Ok(ConfigVersion::V1),
Some(other) => {
anyhow::bail!(
"Unsupported config schema version: {}. This package supports versions 0-1.",
other
);
}
None => {
anyhow::bail!("Config schema_version field must be an integer, got: {:?}", v);
}
},
}
}
pub fn load_with_migration(json_str: &str) -> Result<ServerConfig> {
let mut value: Value = serde_json::from_str(json_str)
.context("Failed to parse config JSON")?;
let version = detect_version(&value)?;
log::info!("Config schema version: {:?}", version);
let mut current_version = version;
if current_version == ConfigVersion::V0 {
log::info!("Migrating config: v0 → v1");
migrate_v0_to_v1(&mut value)?;
current_version = ConfigVersion::V1;
}
let current_num = match current_version {
ConfigVersion::V0 => 0,
ConfigVersion::V1 => 1,
};
if current_num != ServerConfig::CURRENT_SCHEMA_VERSION as i32 {
log::warn!(
"Config version {} differs from supported version {}. Using anyway.",
current_num,
ServerConfig::CURRENT_SCHEMA_VERSION
);
}
let mut config: ServerConfig = serde_json::from_value(value)
.context("Failed to deserialize config after migration")?;
config.validate_and_repair()
.map_err(|e| anyhow::anyhow!("Config validation failed: {}", e))?;
Ok(config)
}
fn migrate_v0_to_v1(value: &mut Value) -> Result<()> {
if let Some(obj) = value.as_object_mut() {
obj.insert("schema_version".to_string(), serde_json::json!(1));
log::info!("Added schema_version field to config");
}
Ok(())
}