use lino_objects_codec::LinoValue;
use lino_rest_api::codec::{decode, decode_single_line, encode, encode_single_line};
use serde_json::Value as Json;
const FIXTURES: &str = include_str!("fixtures/readable-format-cases.json");
const LANGUAGE: &str = "rust";
struct Case {
name: String,
value: LinoValue,
text: String,
line: String,
}
fn materialise(encoded: &Json) -> LinoValue {
let object = encoded.as_object().expect("a fixture value is an object");
let (kind, payload) = object
.iter()
.next()
.expect("a fixture value names its type");
match kind.as_str() {
"null" => LinoValue::Null,
"bool" => LinoValue::Bool(payload.as_bool().expect("a boolean payload")),
"int" => LinoValue::Int(payload.as_i64().expect("an integer payload")),
"str" => LinoValue::String(payload.as_str().expect("a string payload").to_string()),
"float" => LinoValue::Float(match payload {
Json::String(name) => match name.as_str() {
"NaN" => f64::NAN,
"Infinity" => f64::INFINITY,
"-Infinity" => f64::NEG_INFINITY,
other => panic!("Unknown float name in a fixture: {other}"),
},
other => other.as_f64().expect("a numeric payload"),
}),
"array" => LinoValue::Array(
payload
.as_array()
.expect("an array payload")
.iter()
.map(materialise)
.collect(),
),
"object" => LinoValue::Object(
payload
.as_array()
.expect("an object payload is a list of pairs")
.iter()
.map(|pair| {
let pair = pair.as_array().expect("a pair");
let key = pair[0].as_str().expect("a textual key").to_string();
(key, materialise(&pair[1]))
})
.collect(),
),
other => panic!("Unknown fixture value kind: {other}"),
}
}
fn equal(left: &LinoValue, right: &LinoValue) -> bool {
match (left, right) {
(LinoValue::Float(left), LinoValue::Float(right)) => {
left == right || (left.is_nan() && right.is_nan())
}
(LinoValue::Array(left), LinoValue::Array(right)) => {
left.len() == right.len() && left.iter().zip(right).all(|(l, r)| equal(l, r))
}
(LinoValue::Object(left), LinoValue::Object(right)) => {
left.len() == right.len()
&& left
.iter()
.zip(right)
.all(|((lk, lv), (rk, rv))| lk == rk && equal(lv, rv))
}
(left, right) => left == right,
}
}
fn cases() -> Vec<Case> {
let document: Json = serde_json::from_str(FIXTURES).expect("the fixtures are well-formed JSON");
document["cases"]
.as_array()
.expect("the fixtures carry a list of cases")
.iter()
.filter(|case| case["skip"].get(LANGUAGE).is_none())
.map(|case| Case {
name: case["name"].as_str().expect("a named case").to_string(),
value: materialise(&case["value"]),
text: case["text"].as_str().expect("an indented form").to_string(),
line: case["line"]
.as_str()
.expect("a single line form")
.to_string(),
})
.collect()
}
fn report(failures: Vec<String>) {
assert!(
failures.is_empty(),
"{} case(s) did not match:\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn the_fixtures_are_the_ones_the_other_packages_run() {
let cases = cases();
assert!(cases.len() >= 50, "only {} cases were loaded", cases.len());
assert!(cases.iter().any(|case| case.name == "null_scalar"));
assert!(
cases
.iter()
.any(|case| case.name == "deeply_nested_objects")
);
}
#[test]
fn indented_encoding_is_byte_identical_across_languages() {
let failures = cases()
.into_iter()
.filter_map(|case| {
let encoded = encode(&case.value);
(encoded != case.text).then(|| {
format!(
"{}: encoded {encoded:?}, expected {:?}",
case.name, case.text
)
})
})
.collect();
report(failures);
}
#[test]
fn single_line_encoding_is_byte_identical_across_languages() {
let failures = cases()
.into_iter()
.filter_map(|case| {
let encoded = encode_single_line(&case.value);
(encoded != case.line).then(|| {
format!(
"{}: encoded {encoded:?}, expected {:?}",
case.name, case.line
)
})
})
.collect();
report(failures);
}
#[test]
fn both_published_forms_round_trip() {
let mut failures = Vec::new();
for case in cases() {
match decode(&case.text) {
Ok(decoded) if equal(&decoded, &case.value) => {}
Ok(decoded) => failures.push(format!(
"{}: the indented form decoded to {decoded:?}, expected {:?}",
case.name, case.value
)),
Err(error) => failures.push(format!(
"{}: the indented form did not decode: {error}",
case.name
)),
}
match decode_single_line(&case.line) {
Ok(decoded) if equal(&decoded, &case.value) => {}
Ok(decoded) => failures.push(format!(
"{}: the single line form decoded to {decoded:?}, expected {:?}",
case.name, case.value
)),
Err(error) => failures.push(format!(
"{}: the single line form did not decode: {error}",
case.name
)),
}
}
report(failures);
}