use okf::yaml::Value;
fn roundtrip(src: &str) -> Value {
let v = Value::parse(src).unwrap();
let emitted = v.to_yaml_string();
let reparsed = Value::parse(&emitted).unwrap();
assert_eq!(v, reparsed, "round-trip mismatch.\nsrc:\n{src}\nemitted:\n{emitted}");
v
}
#[test]
fn scalars() {
assert_eq!(Value::parse("hello").unwrap(), Value::String("hello".into()));
assert_eq!(Value::parse("42").unwrap(), Value::Int(42));
assert_eq!(Value::parse("-7").unwrap(), Value::Int(-7));
assert_eq!(Value::parse("2.5").unwrap(), Value::Float(2.5));
assert_eq!(Value::parse("true").unwrap(), Value::Bool(true));
assert_eq!(Value::parse("false").unwrap(), Value::Bool(false));
assert_eq!(Value::parse("null").unwrap(), Value::Null);
assert_eq!(Value::parse("~").unwrap(), Value::Null);
assert_eq!(Value::parse("").unwrap(), Value::Null);
}
#[test]
fn quoted_scalars() {
assert_eq!(Value::parse("\"42\"").unwrap(), Value::String("42".into()));
assert_eq!(Value::parse("'true'").unwrap(), Value::String("true".into()));
assert_eq!(
Value::parse("\"line1\\nline2\"").unwrap(),
Value::String("line1\nline2".into())
);
assert_eq!(
Value::parse("'it''s here'").unwrap(),
Value::String("it's here".into())
);
}
#[test]
fn block_mapping() {
let v = roundtrip("type: BigQuery Table\ntitle: Orders\ncount: 3\n");
let m = v.as_mapping().unwrap();
assert_eq!(m.get("type").unwrap().as_str(), Some("BigQuery Table"));
assert_eq!(m.get("count").unwrap().as_int(), Some(3));
assert_eq!(m.keys().collect::<Vec<_>>(), vec!["type", "title", "count"]);
}
#[test]
fn flow_and_block_sequences() {
let flow = roundtrip("tags: [sales, orders, revenue]\n");
assert_eq!(
flow.as_mapping().unwrap().get("tags").unwrap().as_sequence().unwrap().len(),
3
);
let block = roundtrip("tags:\n - sales\n - orders\n");
let tags = block.as_mapping().unwrap().get("tags").unwrap();
assert_eq!(tags.as_sequence().unwrap()[0].as_str(), Some("sales"));
}
#[test]
fn nested_mappings() {
roundtrip("a:\n b:\n c: deep\n d: 2\ne: top\n");
}
#[test]
fn flow_mapping() {
let v = roundtrip("obj: {x: 1, y: two}\n");
let obj = v.as_mapping().unwrap().get("obj").unwrap().as_mapping().unwrap();
assert_eq!(obj.get("x").unwrap().as_int(), Some(1));
assert_eq!(obj.get("y").unwrap().as_str(), Some("two"));
}
#[test]
fn comments_are_ignored() {
let v = Value::parse("# leading comment\ntype: X # trailing\ntitle: Y\n").unwrap();
let m = v.as_mapping().unwrap();
assert_eq!(m.get("type").unwrap().as_str(), Some("X"));
assert_eq!(m.get("title").unwrap().as_str(), Some("Y"));
}
#[test]
fn literal_block_scalar() {
let v = Value::parse("body: |\n line one\n line two\n").unwrap();
assert_eq!(
v.as_mapping().unwrap().get("body").unwrap().as_str(),
Some("line one\nline two\n")
);
}
#[test]
fn folded_block_scalar() {
let v = Value::parse("body: >\n line one\n line two\n").unwrap();
assert_eq!(
v.as_mapping().unwrap().get("body").unwrap().as_str(),
Some("line one line two\n")
);
}
#[test]
fn strings_needing_quotes_roundtrip() {
for s in ["42", "true", "null", "a: b", "value # x", "", " spaced "] {
let v = Value::String(s.to_string());
let emitted = Value::Mapping({
let mut m = okf::yaml::Mapping::new();
m.insert("k", v.clone());
m
})
.to_yaml_string();
let reparsed = Value::parse(&emitted).unwrap();
assert_eq!(
reparsed.as_mapping().unwrap().get("k"),
Some(&v),
"string {s:?} did not round-trip; emitted: {emitted}"
);
}
}
#[test]
fn block_sequence_at_parent_indent() {
let v = Value::parse("type: X\ntags:\n- sales\n- orders\ntitle: Y\n").unwrap();
let m = v.as_mapping().unwrap();
let tags = m.get("tags").unwrap().as_sequence().unwrap();
assert_eq!(tags.len(), 2);
assert_eq!(tags[0].as_str(), Some("sales"));
assert_eq!(m.get("title").unwrap().as_str(), Some("Y"));
let nested = Value::parse("outer:\n tags:\n - a\n - b\n").unwrap();
let inner = nested.as_mapping().unwrap().get("outer").unwrap().as_mapping().unwrap();
assert_eq!(inner.get("tags").unwrap().as_sequence().unwrap().len(), 2);
}
#[test]
fn conservative_number_resolution() {
assert_eq!(Value::parse("007").unwrap(), Value::String("007".into()));
assert_eq!(Value::parse("08").unwrap(), Value::String("08".into()));
assert_eq!(Value::parse("1e3").unwrap(), Value::String("1e3".into()));
assert_eq!(Value::parse("1.5e3").unwrap(), Value::Float(1500.0));
assert_eq!(Value::parse("0").unwrap(), Value::Int(0));
assert_eq!(Value::parse("-42").unwrap(), Value::Int(-42));
}
#[test]
fn non_finite_and_large_floats_roundtrip() {
for f in [f64::INFINITY, f64::NEG_INFINITY, 1e30, -2.5e-12, 1.0] {
let v = Value::Float(f);
let mut m = okf::yaml::Mapping::new();
m.insert("k", v.clone());
let emitted = Value::Mapping(m).to_yaml_string();
let reparsed = Value::parse(&emitted).unwrap();
let got = reparsed.as_mapping().unwrap().get("k").unwrap();
match got {
Value::Float(g) => assert_eq!(g.to_bits(), f.to_bits(), "emitted: {emitted}"),
other => panic!("{f} round-tripped as {other:?} (emitted: {emitted})"),
}
}
let mut m = okf::yaml::Mapping::new();
m.insert("k", Value::Float(f64::NAN));
let reparsed = Value::parse(&Value::Mapping(m).to_yaml_string()).unwrap();
assert!(matches!(reparsed.as_mapping().unwrap().get("k"), Some(Value::Float(g)) if g.is_nan()));
}
#[test]
fn unterminated_flow_is_error() {
assert!(Value::parse("tags: [a, b").is_err());
}
#[test]
fn tab_indentation_is_error() {
assert!(Value::parse("a:\n\tb: 1").is_err());
}