use anyhow::Result;
use serde_json::{Map, Value};
pub type MigrationFn = fn(Map<String, Value>) -> Result<Map<String, Value>>;
pub struct MigrationStep {
pub from: &'static str,
pub to: &'static str,
pub apply: MigrationFn,
}
pub fn registered_steps() -> Vec<MigrationStep> {
vec![
MigrationStep {
from: "req-v1",
to: "req-v2",
apply: v1_to_v2,
},
MigrationStep {
from: "req-v2",
to: "req-v3",
apply: v2_to_v3,
},
MigrationStep {
from: "req-v3",
to: "req-v4",
apply: v3_to_v4,
},
]
}
fn v1_to_v2(root: Map<String, Value>) -> Result<Map<String, Value>> {
Ok(root)
}
fn v2_to_v3(root: Map<String, Value>) -> Result<Map<String, Value>> {
Ok(root)
}
fn v3_to_v4(mut root: Map<String, Value>) -> Result<Map<String, Value>> {
fn rename_key(obj: &mut Map<String, Value>) {
if let Some(v) = obj.remove("validation") {
obj.entry("verification").or_insert(v);
}
}
for family in ["requirements", "safety_requirements"] {
if let Some(Value::Object(items)) = root.get_mut(family) {
for item in items.values_mut() {
if let Value::Object(o) = item {
rename_key(o);
}
}
}
}
if let Some(Value::Object(cfg)) = root.get_mut("_config") {
rename_key(cfg);
}
Ok(root)
}
pub fn walk_chain(
mut root: Map<String, Value>,
detected: &str,
target: &str,
) -> Result<(Map<String, Value>, String)> {
if detected == target {
return Ok((root, target.to_string()));
}
let steps = registered_steps();
let mut current = detected.to_string();
let mut applied: Vec<String> = Vec::new();
loop {
if current == target {
return Ok((root, current));
}
let next = steps.iter().find(|s| s.from == current);
match next {
Some(step) => {
root = (step.apply)(root)?;
applied.push(format!("{} → {}", step.from, step.to));
current = step.to.to_string();
}
None => {
return Err(anyhow::anyhow!(
"no migration path from {} to {} (applied so far: {})",
current,
target,
if applied.is_empty() {
"none".to_string()
} else {
applied.join(", ")
}
));
}
}
}
}