use object_hash::{Options, Value, canonical_with, hash_with_options};
mod support;
use support::Reader;
struct Case {
description: String,
exclude_values: bool,
unordered_arrays: bool,
canonical: String,
digest: String,
}
fn cases() -> Vec<Case> {
include_str!("fixtures/objects.tsv")
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| {
let fields: Vec<&str> = line.split('\t').collect();
assert_eq!(fields.len(), 4, "malformed line: {line:?}");
let flags: Vec<char> = fields[1].chars().collect();
Case {
description: fields[0].to_string(),
exclude_values: flags[0] == '1',
unordered_arrays: flags[1] == '1',
canonical: fields[2].to_string(),
digest: fields[3].to_string(),
}
})
.collect()
}
fn options_for(case: &Case) -> Options {
Options {
exclude_values: case.exclude_values,
unordered_arrays: case.unordered_arrays,
..Options::default()
}
}
#[test]
fn every_object_matches_the_reference() {
let cases = cases();
assert!(cases.len() >= 2000, "only {} cases", cases.len());
let mut failures = Vec::new();
for case in &cases {
let mut reader = Reader::new(&case.description);
let value = reader.value();
assert_eq!(
reader.at,
case.description.len(),
"not all of {:?} was read",
case.description
);
let options = options_for(case);
let got = canonical_with(&value, &options);
if got != case.canonical {
failures.push(format!(
"{}\n canonical\n expected {:?}\n got {got:?}",
case.description, case.canonical
));
continue;
}
let got_digest = hash_with_options(&value, options);
if got_digest != case.digest {
failures.push(format!(
"{}\n digest\n expected {}\n got {got_digest}",
case.description, case.digest
));
}
}
assert!(
failures.is_empty(),
"{} failures, first few:\n{}",
failures.len(),
failures
.iter()
.take(10)
.cloned()
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn an_object_and_a_map_with_the_same_entry_differ() {
let object = Value::Object(vec![("a".into(), Value::number(1))]);
let map = Value::Map(vec![("a".into(), Value::number(1))]);
assert_ne!(
canonical_with(&object, &Options::default()),
canonical_with(&map, &Options::default())
);
}
#[test]
fn keys_ignores_the_values_of_an_object() {
let options = Options::keys();
let one = Value::Object(vec![
("a".into(), Value::number(1)),
("b".into(), Value::number(2)),
]);
let two = Value::Object(vec![
("a".into(), Value::number(9)),
("b".into(), Value::Null),
]);
assert_eq!(
canonical_with(&one, &options),
canonical_with(&two, &options)
);
let map_one = Value::Map(vec![("a".into(), Value::number(1))]);
let map_two = Value::Map(vec![("a".into(), Value::number(2))]);
assert_ne!(
canonical_with(&map_one, &options),
canonical_with(&map_two, &options)
);
}
#[test]
fn an_object_is_written_with_unquoted_keys() {
let object = Value::Object(vec![("a".into(), Value::number(1))]);
assert_eq!(
canonical_with(&object, &Options::default()),
"object:1:string:1:a:number:1,"
);
}
#[test]
fn unordered_arrays_makes_order_stop_mattering() {
let forward = Value::Array(vec![Value::number(1), Value::number(2)]);
let backward = Value::Array(vec![Value::number(2), Value::number(1)]);
assert_ne!(
canonical_with(&forward, &Options::default()),
canonical_with(&backward, &Options::default())
);
let options = Options::default().unordered_arrays();
assert_eq!(
canonical_with(&forward, &options),
canonical_with(&backward, &options)
);
}