///|
/// A deterministic, review-friendly change plan derived from two documents.
/// It is descriptive rather than an in-place mutator, so callers can review
/// or approve changes before applying them in their own storage layer.
pub enum ChangeOperation {
Add
Remove
Replace
}
///|
pub(all) struct ChangePlan {
path : String
operation : ChangeOperation
before : String?
after : String?
}
///|
pub fn ChangeOperation::to_string(self : ChangeOperation) -> String {
match self {
Add => "add"
Remove => "remove"
Replace => "replace"
}
}
///|
pub fn ChangeOperation::to_json(self : ChangeOperation) -> String {
canonical_string(self.to_string())
}
///|
/// Convert semantic differences into a stable operation list.
pub fn change_plan(left : JsonValue, right : JsonValue) -> Array[ChangePlan] {
let differences = diff_json(left, right)
let plan : Array[ChangePlan] = []
for difference in differences {
let operation = match difference.kind {
Added => ChangeOperation::Add
Removed => ChangeOperation::Remove
Changed | TypeChanged => ChangeOperation::Replace
}
plan.push({
path: difference.path,
operation,
before: difference.left,
after: difference.right,
})
}
plan
}
///|
pub fn change_plan_documents(
left : String,
right : String,
) -> Result[Array[ChangePlan], String] {
let left_value = match parse(left) {
Ok(value) => value
Err(error) => return Err("left document: " + error.message())
}
let right_value = match parse(right) {
Ok(value) => value
Err(error) => return Err("right document: " + error.message())
}
Ok(change_plan(left_value, right_value))
}
///|
pub fn ChangePlan::to_json(self : ChangePlan) -> String {
let before = match self.before {
Some(value) => value
None => "null"
}
let after = match self.after {
Some(value) => value
None => "null"
}
"{\"op\":" +
canonical_string(self.operation.to_string()) +
",\"path\":" +
canonical_string(self.path) +
",\"before\":" +
before +
",\"after\":" +
after +
"}"
}
///|
pub fn change_plan_text(plan : Array[ChangePlan]) -> String {
let mut output = ""
for change in plan {
if output != "" {
output = output + "\n"
}
output = output +
change.operation.to_string() +
" " +
change.path +
" : " +
(match change.before {
Some(value) => value
None => "-"
}) +
" -> " +
(match change.after {
Some(value) => value
None => "-"
})
}
output
}