///|
/// Describes how one configuration path differs between two values.
pub(all) enum ConfigDiffKind {
Added
Removed
Changed
TypeChanged
} derive(Eq, Debug)
///|
/// A single difference between an earlier and a later configuration value.
pub struct ConfigDiff {
path : ConfigPath
kind : ConfigDiffKind
before : ConfigValue?
after : ConfigValue?
} derive(Eq, Debug)
///|
/// Return the path at which this difference occurred.
pub fn ConfigDiff::path(self : ConfigDiff) -> ConfigPath {
self.path
}
///|
/// Return the category of this difference.
pub fn ConfigDiff::kind(self : ConfigDiff) -> ConfigDiffKind {
self.kind
}
///|
/// Return the earlier value, or `None` when the path was added.
pub fn ConfigDiff::before(self : ConfigDiff) -> ConfigValue? {
self.before
}
///|
/// Return the later value, or `None` when the path was removed.
pub fn ConfigDiff::after(self : ConfigDiff) -> ConfigValue? {
self.after
}
///|
/// Return a stable lowercase name for this difference category.
pub fn ConfigDiffKind::to_string(self : ConfigDiffKind) -> String {
match self {
Added => "added"
Removed => "removed"
Changed => "changed"
TypeChanged => "type_changed"
}
}
///|
/// Append one difference to a collection.
fn push_diff(
differences : Array[ConfigDiff],
segments : Array[String],
kind : ConfigDiffKind,
before : ConfigValue?,
after : ConfigValue?,
) -> Unit {
differences.push({
path: config_path_from_segments(segments),
kind,
before,
after,
})
}
///|
/// Recursively compare two JSON values and collect their differences.
fn diff_raw(
before : Json,
after : Json,
segments : Array[String],
differences : Array[ConfigDiff],
) -> Unit {
if before == after {
return
}
match (before, after) {
(Object(before_fields), Object(after_fields)) => {
let keys = before_fields.keys().to_array()
after_fields
.keys()
.each(key => if !before_fields.contains(key) { keys.push(key) })
keys.sort_by((left, right) => left.lexical_compare(right))
for key in keys {
let next_segments = segments.copy()
next_segments.push(key)
match (before_fields.get(key), after_fields.get(key)) {
(Some(before_value), Some(after_value)) =>
diff_raw(before_value, after_value, next_segments, differences)
(Some(before_value), None) =>
push_diff(
differences,
next_segments,
Removed,
Some({ raw: before_value }),
None,
)
(None, Some(after_value)) =>
push_diff(
differences,
next_segments,
Added,
None,
Some({ raw: after_value }),
)
(None, None) => ()
}
}
}
(before_value, after_value) => {
let before_config = { raw: before_value }
let after_config = { raw: after_value }
let kind = if ConfigValueKind::from_json(before_value) ==
ConfigValueKind::from_json(after_value) {
Changed
} else {
TypeChanged
}
push_diff(
differences,
segments,
kind,
Some(before_config),
Some(after_config),
)
}
}
}
///|
/// Compare this configuration with a later value.
///
/// Object fields are compared recursively. Added and removed object fields are
/// reported as one difference for the whole field. Arrays are compared as whole
/// values in this first diff implementation. Results are ordered by path.
pub fn ConfigValue::diff(
self : ConfigValue,
later : ConfigValue,
) -> Array[ConfigDiff] {
let differences : Array[ConfigDiff] = []
diff_raw(self.raw, later.raw, [], differences)
differences.sort_by((left, right) => {
left.path.to_string().lexical_compare(right.path.to_string())
})
differences
}