use crate::codec::{ConflictRec, Rec, Val};
pub(super) fn same(left: Option<&Val>, right: Option<&Val>) -> bool {
match (present(left), present(right)) {
(None, None) => true,
(Some(left), Some(right)) => same_value(left, right),
_ => false,
}
}
pub(super) fn present(value: Option<&Val>) -> Option<&Val> {
value.filter(|value| !matches!(value, Val::List(items) if items.is_empty()))
}
pub(super) fn same_value(left: &Val, right: &Val) -> bool {
match (left, right) {
(Val::Scalar { text: left, .. }, Val::Scalar { text: right, .. }) => left == right,
(Val::Null { .. }, Val::Null { .. }) => true,
(Val::Ext { value: left, .. }, Val::Ext { value: right, .. }) => left == right,
(Val::List(left), Val::List(right)) => all_same(left, right, same_value),
(Val::Record(left), Val::Record(right)) => same_record(left, right),
_ => false,
}
}
pub(super) fn same_record(left: &Rec, right: &Rec) -> bool {
let (left_fields, right_fields) = (present_fields(left), present_fields(right));
left.kind == right.kind
&& left_fields.len() == right_fields.len()
&& left_fields
.iter()
.zip(&right_fields)
.all(|((left_name, left), (right_name, right))| {
left_name == right_name && same_value(left, right)
})
&& all_same(&left.conflicts, &right.conflicts, same_conflict)
}
pub(super) fn same_conflict(left: &ConflictRec, right: &ConflictRec) -> bool {
left.field == right.field
&& left
.sides()
.into_iter()
.zip(right.sides())
.all(|((_, left), (_, right))| same(left, right))
}
pub(super) fn holds(record: &Rec, conflict: &ConflictRec) -> bool {
record
.conflicts
.iter()
.any(|held| same_conflict(held, conflict))
}
fn all_same<T>(left: &[T], right: &[T], same: fn(&T, &T) -> bool) -> bool {
left.len() == right.len()
&& left
.iter()
.zip(right)
.all(|(left, right)| same(left, right))
}
fn present_fields(record: &Rec) -> Vec<&(&'static str, Val)> {
record
.fields
.iter()
.filter(|(_, value)| present(Some(value)).is_some())
.collect()
}
pub(super) fn text(value: &Val) -> String {
match value {
Val::Scalar { text, .. } => text.clone(),
Val::Null { .. } => "null".to_owned(),
Val::Ext { value, .. } => format!("ext{}", crate::ext::to_json(value)),
Val::List(items) => format!("[{}]", items.iter().map(text).collect::<Vec<_>>().join(",")),
Val::Record(record) => {
let fields = record
.fields
.iter()
.filter(|(_, value)| present(Some(value)).is_some())
.map(|(name, value)| format!("{name}={}", text(value)));
let mut text = format!("{{{}}}", fields.collect::<Vec<_>>().join(";"));
if !record.conflicts.is_empty() {
let conflicts: Vec<String> = record.conflicts.iter().map(conflict_text).collect();
text.push('[');
text.push_str(&conflicts.join(","));
text.push(']');
}
text
}
}
}
fn conflict_text(conflict: &ConflictRec) -> String {
let sides = conflict
.sides()
.into_iter()
.filter_map(|(name, side)| present(side).map(|side| format!("{name}={}", text(side))));
let fields: Vec<String> = std::iter::once(format!("field={}", conflict.field))
.chain(sides)
.collect();
format!("{{{}}}", fields.join(";"))
}