#![allow(missing_docs, clippy::unwrap_used, clippy::expect_used)]
use noyalib::{Value, from_reader, from_slice, from_str, load_all, load_all_as};
const SINGLE: &[(&str, &str)] = &[
("flat scalars", "a: 1\nb: text\nc: true\nd: ~\n"),
("nested", "a:\n b:\n c: 1\n"),
("sequence", "xs:\n - 1\n - two\n"),
("flow", "m: {a: 1}\nxs: [1, 2]\n"),
("anchors", "base: &b 1\nuse: *b\n"),
("a merge key", "base: &b\n a: 1\nm:\n <<: *b\n"),
("tags", "a: !!str 1\nb: !Custom v\n"),
("block scalars", "lit: |\n one\nfold: >\n two\n"),
("quoted", "s: 'one'\nd: \"two\"\n"),
("unicode", "k: \"héllo ✓\"\n"),
("CRLF", "a: 1\r\nb: 2\r\n"),
("a BOM", "\u{feff}a: 1\n"),
("no trailing newline", "a: 1\nb: 2"),
("comments", "# head\na: 1 # side\n"),
("empty collections", "m: {}\nxs: []\n"),
("duplicate keys", "k: 1\nk: 2\n"),
];
const MULTI: &[(&str, &str)] = &[
("two plain documents", "a: 1\n---\nb: 2\n"),
("three documents", "a: 1\n---\nb: 2\n---\nc: 3\n"),
("explicit end markers", "a: 1\n...\n---\nb: 2\n...\n"),
("a leading marker", "---\na: 1\n---\nb: 2\n"),
(
"documents of different shapes",
"a: 1\n---\n- 1\n- 2\n---\nscalar\n",
),
(
"a document with comments",
"# one\na: 1\n---\n# two\nb: 2\n",
),
("CRLF between documents", "a: 1\r\n---\r\nb: 2\r\n"),
("a single document", "only: 1\n"),
("anchors within one document", "x: &a 1\ny: *a\n---\nz: 2\n"),
];
#[test]
fn the_three_input_forms_agree() {
let mut disagreements = Vec::new();
for (label, yaml) in SINGLE {
let by_str = from_str::<Value>(yaml);
let by_slice = from_slice::<Value>(yaml.as_bytes());
let by_reader = from_reader::<_, Value>(yaml.as_bytes());
let shape = |r: &noyalib::Result<Value>| match r {
Ok(v) => format!("ok:{v:?}"),
Err(_) => "err".to_string(),
};
let (a, b, c) = (shape(&by_str), shape(&by_slice), shape(&by_reader));
if a != b {
disagreements.push(format!(
"{label}: &str vs &[u8]\n str: {a}\n slice: {b}"
));
}
if a != c {
disagreements.push(format!(
"{label}: &str vs Read\n str: {a}\n reader: {c}"
));
}
}
assert!(
disagreements.is_empty(),
"{} input form(s) disagree:\n{}",
disagreements.len(),
disagreements.join("\n")
);
}
#[test]
fn the_multi_document_readers_agree() {
let mut disagreements = Vec::new();
for (label, yaml) in MULTI {
let via_load_all = load_all(yaml).and_then(|it| it.collect::<Result<Vec<Value>, _>>());
let via_load_all_as = load_all_as::<Value>(yaml);
match (&via_load_all, &via_load_all_as) {
(Ok(a), Ok(b)) if a != b => disagreements.push(format!(
"{label}: load_all vs load_all_as\n load_all: {a:?}\n load_all_as: {b:?}"
)),
(Err(e), Ok(_)) => {
disagreements.push(format!(
"{label}: load_all refused ({e}), load_all_as accepted"
));
}
(Ok(_), Err(e)) => {
disagreements.push(format!(
"{label}: load_all_as refused ({e}), load_all accepted"
));
}
_ => {}
}
}
assert!(
disagreements.is_empty(),
"{} multi-document reader disagreement(s):\n{}",
disagreements.len(),
disagreements.join("\n")
);
}
#[cfg(feature = "parallel")]
#[test]
fn the_parallel_reader_agrees_with_the_sequential_one() {
let mut disagreements = Vec::new();
for (label, yaml) in MULTI {
let sequential = load_all_as::<Value>(yaml);
let parallel = noyalib::parallel::parse::<Value>(yaml);
match (&sequential, ¶llel) {
(Ok(s), Ok(p)) if s != p => disagreements.push(format!(
"{label}: order or content differs\n sequential: {s:?}\n parallel: {p:?}"
)),
(Err(e), Ok(_)) => {
disagreements.push(format!(
"{label}: sequential refused ({e}), parallel accepted"
));
}
(Ok(_), Err(e)) => {
disagreements.push(format!(
"{label}: parallel refused ({e}), sequential accepted"
));
}
_ => {}
}
}
assert!(
disagreements.is_empty(),
"{} parallel/sequential disagreement(s):\n{}",
disagreements.len(),
disagreements.join("\n")
);
}
#[cfg(feature = "parallel")]
#[test]
fn the_parallel_split_finds_the_same_document_boundaries() {
let mut disagreements = Vec::new();
for (label, yaml) in MULTI {
let Ok(sequential) = load_all_as::<Value>(yaml) else {
continue;
};
let pieces = noyalib::parallel::split(yaml);
if pieces.len() != sequential.len() {
disagreements.push(format!(
"{label}: split found {} piece(s), the reader found {} document(s)",
pieces.len(),
sequential.len()
));
}
match noyalib::parallel::values(yaml) {
Ok(v) if v != sequential => disagreements.push(format!(
"{label}: parallel::values differs\n values: {v:?}\n seq: {sequential:?}"
)),
Err(e) => disagreements.push(format!("{label}: parallel::values refused: {e}")),
Ok(_) => {}
}
}
assert!(
disagreements.is_empty(),
"{} parallel-split disagreement(s):\n{}",
disagreements.len(),
disagreements.join("\n")
);
}
#[cfg(feature = "compat-serde-yaml")]
const DOCUMENTED_FACADE_DEVIATIONS: &[(&str, &str)] = &[(
"a merge key",
"`<<` merge keys stay literal entries (alias values resolved) — \
serde_yaml 0.9 requires an explicit merge step",
)];
#[cfg(feature = "compat-serde-yaml")]
#[test]
fn the_compat_facade_differs_only_where_it_documents() {
let mut undocumented = Vec::new();
let mut documented_but_agreed = Vec::new();
for (label, yaml) in SINGLE {
let native = from_str::<Value>(yaml);
let facade = noyalib::compat::serde_yaml::from_str::<Value>(yaml);
let expected = DOCUMENTED_FACADE_DEVIATIONS
.iter()
.find(|(l, _)| l == label);
let differs = match (&native, &facade) {
(Ok(n), Ok(f)) if n != f => Some(format!("native {n:?} vs facade {f:?}")),
(Err(e), Ok(_)) => Some(format!("native refused ({e}), facade accepted")),
(Ok(_), Err(e)) => Some(format!("facade refused ({e}), native accepted")),
_ => None,
};
match (differs, expected) {
(Some(d), None) => undocumented.push(format!("{label}: {d}")),
(None, Some((_, why))) => documented_but_agreed
.push(format!("{label}: documented to differ ({why}) but agreed")),
_ => {}
}
}
assert!(
undocumented.is_empty(),
"{} undocumented difference(s) between the façade and native:\n{}",
undocumented.len(),
undocumented.join("\n")
);
assert!(
documented_but_agreed.is_empty(),
"{} documented deviation(s) no longer apply — update the shim's docs \
and this list:\n{}",
documented_but_agreed.len(),
documented_but_agreed.join("\n")
);
}
#[cfg(feature = "compat-serde-yaml")]
#[test]
fn the_facade_reproduces_serde_yamls_documented_quirks() {
use noyalib::compat::serde_yaml as syml;
let v: Value = syml::from_str("b: &b\n a: 1\nm:\n <<: *b\n").expect("merge");
assert!(
v.get("m").and_then(|m| m.get("<<")).is_some(),
"the façade resolved a merge key; serde_yaml 0.9 leaves it literal: {v:?}"
);
let v: Value = syml::from_str("a: 0123\n").expect("leading zero");
assert_eq!(
v.get("a").and_then(Value::as_str),
Some("0123"),
"a leading-zero integer should stay a string: {v:?}"
);
let v: Value = syml::from_str("a: 0b11\n").expect("binary literal");
assert_eq!(
v.get("a").and_then(Value::as_i64),
Some(3),
"0b11 should read as 3: {v:?}"
);
assert!(
syml::from_str::<Value>("? [a, b]\n: 1\n").is_err(),
"the façade should refuse a non-scalar key"
);
}
#[test]
fn no_reader_panics_or_invents_data_on_a_truncated_stream() {
for (label, yaml) in SINGLE {
for cut in 0..yaml.len() {
if !yaml.is_char_boundary(cut) {
continue;
}
let prefix = &yaml[..cut];
let _ = from_str::<Value>(prefix);
let _ = from_slice::<Value>(prefix.as_bytes());
let _ = from_reader::<_, Value>(prefix.as_bytes());
let _ = load_all_as::<Value>(prefix);
#[cfg(feature = "parallel")]
let _ = noyalib::parallel::parse::<Value>(prefix);
}
let _ = label;
}
}