Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use std::collections::HashMap;
/// Largest integer JavaScript can hold exactly; beyond it the three languages disagree.
const SAFE_INTEGER: i64 = 9007199254740991;
/// A record's key as text, rendered the way collections.group-by-key renders a
/// group name so the two agree on what "the same key" means. None when absent.
///
/// # Panics
/// Panics on a float, a list or a map, or an integer outside the safe range.
fn key_of(value: &Value, key: &str) -> Option<String> {
match value {
Value::Null => None,
Value::Str(s) => Some(s.clone()),
Value::Bool(b) => Some((if *b { "true" } else { "false" }).to_string()),
Value::Int(i) => {
if i.abs() > SAFE_INTEGER {
panic!("cannot diff by the out-of-range number {} at \"{}\"", i, key);
}
Some(i.to_string())
}
Value::Float(f) => panic!("cannot diff by the fractional number {} at \"{}\"", f, key),
_ => panic!("cannot diff by the list or map at \"{}\"", key),
}
}
/// Deep JSON equality, spelled out so all three languages agree: numbers by
/// value (1 equals 1.0), no coercion between types (true is not 1, "1" is not
/// 1), lists in order, and a missing map field equal to a null one.
fn same(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Null, Value::Null) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Int(x), Value::Int(y)) => x == y,
(Value::Int(_), Value::Float(_)) | (Value::Float(_), Value::Int(_)) | (Value::Float(_), Value::Float(_)) => {
a.as_f64() == b.as_f64()
}
(Value::Str(x), Value::Str(y)) => x == y,
(Value::Arr(x), Value::Arr(y)) => x.len() == y.len() && x.iter().zip(y).all(|(p, q)| same(p, q)),
(Value::Obj(x), Value::Obj(y)) => {
// `get` answers Null for a missing field, which is the rule.
x.iter().all(|(k, v)| same(v, b.get(k))) && y.iter().all(|(k, v)| same(a.get(k), v))
}
_ => false,
}
}
fn field_names(record: &Value) -> Vec<String> {
match record {
Value::Obj(pairs) => pairs.iter().map(|(k, _)| k.clone()).collect(),
_ => Vec::new(),
}
}
/// Key every record of one list, refusing missing and duplicate keys.
///
/// # Panics
/// Panics on a record with no key and on a duplicate key.
fn index(records: &[Value], key: &str, side: &str) -> (Vec<String>, HashMap<String, usize>) {
let mut order: Vec<String> = Vec::new();
let mut by_key: HashMap<String, usize> = HashMap::new();
for (i, record) in records.iter().enumerate() {
let k = match key_of(record.get(key), key) {
Some(k) => k,
None => panic!("record {} in {} has no value at \"{}\"", i, side, key),
};
// Picking one of two duplicates would report changes that never happened.
if by_key.contains_key(&k) {
panic!("duplicate key \"{}\" in {}", k, side);
}
by_key.insert(k.clone(), i);
order.push(k);
}
(order, by_key)
}
/// Compare two lists of records matched by `key`: what was added, what was
/// removed, and what changed and in which fields.
///
/// # Panics
/// Panics on an empty key, a record without a key, a duplicate key, or a key
/// value that cannot be rendered.
pub fn diff_by_key(before: &[Value], after: &[Value], key: &str) -> RecordDiff<Value> {
if key.is_empty() {
panic!("diff_by_key needs a non-empty key name");
}
let (before_order, before_keys) = index(before, key, "before");
let (after_order, after_keys) = index(after, key, "after");
let mut added: Vec<Value> = Vec::new();
let mut changed: Vec<RecordChange<Value>> = Vec::new();
let mut unchanged: i64 = 0;
for (i, k) in after_order.iter().enumerate() {
let next = &after[i];
let prev = match before_keys.get(k) {
Some(j) => &before[*j],
None => {
added.push(next.clone());
continue;
}
};
let mut names = field_names(prev);
for name in field_names(next) {
if !names.contains(&name) {
names.push(name);
}
}
let mut fields: Vec<String> = names
.into_iter()
.filter(|name| !same(prev.get(name), next.get(name)))
.collect();
// String ordering in Rust is byte order, which for UTF-8 is code point order.
fields.sort();
if fields.is_empty() {
unchanged += 1;
} else {
changed.push(RecordChange {
key: k.clone(),
before: prev.clone(),
after: next.clone(),
fields,
});
}
}
let removed: Vec<Value> = before_order
.iter()
.enumerate()
.filter(|(_, k)| !after_keys.contains_key(*k))
.map(|(j, _)| before[j].clone())
.collect();
RecordDiff {
added,
removed,
changed,
unchanged,
}
}
pub fn record_change_to_value(change: &RecordChange<Value>) -> Value {
Value::obj(vec![
("key", Value::str(&change.key)),
("before", change.before.clone()),
("after", change.after.clone()),
("fields", Value::Arr(change.fields.iter().map(|f| Value::str(f)).collect())),
])
}
pub fn record_diff_to_value(diff: &RecordDiff<Value>) -> Value {
Value::obj(vec![
("added", Value::Arr(diff.added.clone())),
("removed", Value::Arr(diff.removed.clone())),
("changed", Value::Arr(diff.changed.iter().map(record_change_to_value).collect())),
("unchanged", Value::Int(diff.unchanged)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
record_diff_to_value(&diff_by_key(args[0].as_arr(), args[1].as_arr(), args[2].as_str()))
}