use alloc::collections::{BTreeMap, BTreeSet};
use core::str;
use serde_json::Value;
pub(crate) type Key = Vec<String>;
pub(crate) type Leaves = BTreeMap<Key, Value>;
#[derive(Clone, Debug, Default)]
pub(crate) struct Written {
pub leaves: Vec<(Key, Value)>,
pub tables: BTreeSet<Key>,
pub source: String,
}
pub(crate) fn encode(leaves: &Leaves) -> Vec<u8> {
let line = |(key, value): (&Key, &Value)| {
format!("{}\t{}\n", serde_json::to_string(key).unwrap_or_default(), canonical(value))
};
leaves.iter().map(line).collect::<String>().into_bytes()
}
pub(crate) fn decode(content: &[u8]) -> Option<Leaves> {
str::from_utf8(content)
.ok()?
.lines()
.map(|line| {
let (key, value) = line.split_once('\t')?;
Some((serde_json::from_str(key).ok()?, serde_json::from_str(value).ok()?))
})
.collect()
}
fn canonical(value: &Value) -> String {
let mut sorted = value.clone();
sorted.sort_all_objects();
sorted.to_string()
}
pub(crate) fn walk(value: &Value, prefix: &mut Key, into: &mut Vec<(Key, Value)>) {
match value {
Value::Object(object) => {
for (key, value) in object {
prefix.push(key.clone());
walk(value, prefix, into);
prefix.pop();
}
},
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) | Value::Array(_) => {
into.push((prefix.clone(), value.clone()));
},
}
}
pub(crate) fn lookup<'a>(value: &'a Value, key: &[String]) -> Option<&'a Value> {
key.iter().try_fold(value, |node, segment| node.as_object()?.get(segment))
}
pub(crate) fn display(key: &[String]) -> String {
let plain = |s: &str| {
!s.is_empty() && s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
};
let segments: Vec<String> = key
.iter()
.map(|s| if plain(s) { s.clone() } else { Value::String(s.clone()).to_string() })
.collect();
segments.join(".")
}
pub(crate) fn overlap(a: &[String], b: &[String]) -> bool {
a.starts_with(b) || b.starts_with(a)
}
#[derive(Debug, Default, PartialEq, Eq)]
pub(crate) struct Merge {
pub leaves: Leaves,
pub conflicts: BTreeSet<Key>,
}
pub(crate) fn merge(base: &Leaves, ours: &Leaves, theirs: &Leaves, keys: &BTreeSet<Key>) -> Merge {
let mut merge = Merge::default();
for key in keys {
let (b, o, t) = (base.get(key), ours.get(key), theirs.get(key));
let taken = if o == t || t == b {
o
} else if o == b {
t
} else {
merge.conflicts.insert(key.clone());
o
};
if let Some(value) = taken {
merge.leaves.insert(key.clone(), value.clone());
}
}
merge
}
#[cfg(test)]
mod tests {
use alloc::collections::BTreeSet;
use serde_json::json;
use super::{Leaves, decode, display, encode, merge, overlap};
fn leaves(entries: &[(&[&str], serde_json::Value)]) -> Leaves {
entries
.iter()
.map(|(key, value)| (key.iter().map(|s| (*s).to_owned()).collect(), value.clone()))
.collect()
}
#[test]
fn content_round_trips_whatever_the_key_order() {
let a = leaves(&[(&["a", "b"], json!({ "y": 1, "x": [1, 2] })), (&["c"], json!("d"))]);
let b = leaves(&[(&["c"], json!("d")), (&["a", "b"], json!({ "x": [1, 2], "y": 1 }))]);
assert_eq!(encode(&a), encode(&b), "equal leaves encode equally");
assert_eq!(decode(&encode(&a)), Some(a), "and decode to themselves");
}
#[test]
fn leaves_merge_one_by_one() {
let base = leaves(&[
(&["a"], json!(1)),
(&["b"], json!(1)),
(&["c"], json!(1)),
(&["gone"], json!(1)),
]);
let ours = leaves(&[
(&["a"], json!(2)),
(&["b"], json!(1)),
(&["c"], json!(2)),
(&["gone"], json!(1)),
]);
let theirs = leaves(&[
(&["a"], json!(1)),
(&["b"], json!(3)),
(&["c"], json!(3)),
(&["new"], json!(1)),
]);
let keys: BTreeSet<_> = base.keys().chain(theirs.keys()).cloned().collect();
let merged = merge(&base, &ours, &theirs, &keys);
let want = leaves(&[
(&["a"], json!(2)),
(&["b"], json!(3)),
(&["c"], json!(2)),
(&["new"], json!(1)),
]);
assert_eq!(merged.leaves, want, "ours, theirs, a conflict kept as ours, a drop and an add");
assert_eq!(merged.conflicts.len(), 1, "only the leaf both changed conflicts");
}
#[test]
fn keys_display_and_overlap() {
let key = |parts: &[&str]| parts.iter().map(|s| (*s).to_owned()).collect::<Vec<_>>();
assert_eq!(display(&key(&["workspace", "lints"])), "workspace.lints", "plain");
assert_eq!(
display(&key(&["[rust]", "editor.tabSize"])),
r#""[rust]"."editor.tabSize""#,
"quoted"
);
assert!(overlap(&key(&["a", "b"]), &key(&["a"])), "a key under another overlaps it");
assert!(!overlap(&key(&["a", "b"]), &key(&["a", "c"])), "siblings do not");
}
}