use noyalib::{Spanned, Tag, TaggedValue, Value, from_str, from_value, to_string_value};
use serde::Deserialize;
#[test]
fn round_trip_tagged_scalar() {
let yaml = "!Custom 'hello'\n";
let v: Value = from_str(yaml).unwrap();
assert!(matches!(v, Value::Tagged(_)));
let emitted = to_string_value(&v).unwrap();
let v2: Value = from_str(&emitted).unwrap();
assert!(
matches!(v2, Value::Tagged(_)),
"re-parse should also be Tagged, got {:?}",
v2
);
let (Value::Tagged(t1), Value::Tagged(t2)) = (&v, &v2) else {
panic!()
};
assert_eq!(
t1.tag().as_str(),
t2.tag().as_str(),
"tag survives roundtrip"
);
assert_eq!(
t1.value().as_str(),
t2.value().as_str(),
"inner survives roundtrip"
);
}
#[test]
fn round_trip_tagged_sequence() {
let yaml = "!List [1, 2, 3]\n";
let v: Value = from_str(yaml).unwrap();
assert!(matches!(v, Value::Tagged(_)));
let emitted = to_string_value(&v).unwrap();
let v2: Value = from_str(&emitted).unwrap();
assert!(matches!(v2, Value::Tagged(_)), "re-parse should be Tagged");
let (Value::Tagged(t1), Value::Tagged(t2)) = (&v, &v2) else {
panic!()
};
assert_eq!(t1.tag().as_str(), t2.tag().as_str());
}
#[test]
fn round_trip_tagged_mapping() {
let yaml = "!Map\nk: v\nx: 1\n";
let v: Value = from_str(yaml).unwrap();
assert!(matches!(v, Value::Tagged(_)));
let emitted = to_string_value(&v).unwrap();
let v2: Value = from_str(&emitted).unwrap();
assert!(matches!(v2, Value::Tagged(_)));
let (Value::Tagged(t1), Value::Tagged(t2)) = (&v, &v2) else {
panic!()
};
assert_eq!(t1.tag().as_str(), t2.tag().as_str());
}
#[test]
fn from_value_value_identity_with_tagged() {
let original = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!Custom"),
Value::String("hello".to_string()),
)));
let copy: Value = from_value(&original).unwrap();
assert!(
matches!(copy, Value::Tagged(_)),
"TypeId fast-path: Tagged identity preserved"
);
let (Value::Tagged(a), Value::Tagged(b)) = (&original, ©) else {
panic!()
};
assert_eq!(a.tag().as_str(), b.tag().as_str());
assert_eq!(a.value().as_str(), b.value().as_str());
}
#[test]
fn typed_target_sees_through_tagged_collection() {
use std::collections::HashMap;
let m: HashMap<String, i64> = from_str("!Custom\nk: 1\nx: 2\n").unwrap();
assert_eq!(m["k"], 1);
assert_eq!(m["x"], 2);
let v: Vec<i64> = from_str("!Custom\n- 10\n- 20\n").unwrap();
assert_eq!(v, [10, 20]);
}
#[test]
fn spanned_value_with_tagged_scalar_known_limitation() {
#[derive(Deserialize)]
struct Cfg {
value: Spanned<Value>,
}
let cfg: Cfg = from_str("value: !Custom 'hi'\n").unwrap();
assert!(cfg.value.start.line() >= 1, "span info still works");
assert!(
matches!(cfg.value.value, Value::String(_)),
"current behaviour: Spanned<Value> unwraps the tag, got {:?}",
cfg.value.value
);
}
#[test]
fn alias_to_tagged_scalar() {
let yaml = "a: !Color &c '#ff8800'\nb: *c\n";
let v: Value = from_str(yaml).unwrap();
let a = v.get("a").unwrap();
let b = v.get("b").unwrap();
assert!(matches!(a, Value::Tagged(_)));
assert!(matches!(b, Value::Tagged(_)));
let (Value::Tagged(ta), Value::Tagged(tb)) = (a, b) else {
panic!()
};
assert_eq!(ta.tag().as_str(), tb.tag().as_str());
assert_eq!(ta.value().as_str(), tb.value().as_str());
}
#[test]
fn multi_doc_with_tagged_doc() {
let yaml = "---\n!Foo 7\n---\nplain: 8\n";
let docs: Vec<Value> = noyalib::load_all_as(yaml).unwrap();
assert_eq!(docs.len(), 2);
assert!(matches!(docs[0], Value::Tagged(_)));
assert!(matches!(docs[1], Value::Mapping(_)));
}
#[test]
fn interpolate_into_tagged_inner() {
use std::collections::HashMap;
let mut props: HashMap<String, String> = HashMap::new();
let _ = props.insert("HOST".into(), "api.example.com".into());
let mut v: Value = from_str("server: !Custom ${HOST}\n").unwrap();
v.interpolate_properties(&props).unwrap();
let server = v.get("server").unwrap();
let Value::Tagged(t) = server else {
panic!("expected Tagged after interpolate, got {:?}", server);
};
assert_eq!(t.tag().as_str(), "!Custom");
assert_eq!(
t.value().as_str(),
Some("api.example.com"),
"interpolate must walk into the inner of a Tagged"
);
}
#[test]
fn tag_registry_strips_registered_tag() {
use std::sync::Arc;
let reg = Arc::new(noyalib::TagRegistry::new().with("!Color"));
let cfg = noyalib::ParserConfig::new().tag_registry(reg);
let v: Value = noyalib::from_str_with_config("!Color '#ff8800'\n", &cfg).unwrap();
assert!(
matches!(v, Value::String(_)),
"registered tag should strip through, got {:?}",
v
);
assert_eq!(v.as_str(), Some("#ff8800"));
}
#[test]
fn borrowed_value_tagged_scalar() {
use noyalib::borrowed::from_str_borrowed;
let yaml = "!Custom 'hi'\n";
let v = from_str_borrowed(yaml).unwrap();
let owned = v.into_owned();
eprintln!("BorrowedValue -> Value: {:?}", owned);
let _ = owned;
}
#[cfg(feature = "compat-serde-yaml")]
#[test]
fn compat_shim_preserves_old_behaviour() {
use noyalib::compat::serde_yaml::{Value as CompatValue, from_str as compat_from_str};
let yaml = "!Custom 'hello'\n";
let v: CompatValue = compat_from_str(yaml).unwrap();
eprintln!("compat shim sees: {:?}", v);
let _ = v;
}
#[test]
fn tagged_equality() {
let a = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!X"),
Value::String("a".to_string()),
)));
let b = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!X"),
Value::String("a".to_string()),
)));
let c = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!Y"),
Value::String("a".to_string()),
)));
let d = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!X"),
Value::String("z".to_string()),
)));
assert_eq!(a, b);
assert_ne!(a, c);
assert_ne!(a, d);
}
#[test]
fn tagged_display() {
let v = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!Custom"),
Value::String("hello".to_string()),
)));
let s = format!("{}", v);
assert!(
s.contains("Custom") || s.contains("hello"),
"Display should mention the tag or inner: {}",
s
);
}
#[test]
fn value_via_serde_json() {
let v: Value = from_str("port: 8080\nhost: localhost\n").unwrap();
let json = serde_json::to_string(&v).unwrap();
let back: Value = serde_json::from_str(&json).unwrap();
assert_eq!(v.get_path("port"), back.get_path("port"));
assert_eq!(v.get_path("host"), back.get_path("host"));
}
#[test]
fn nested_tags_survive_outer_tag_c4hz_regression() {
let yaml = "!shape\n- !circle 1\n- !line 2\n";
let v: Value = from_str(yaml).unwrap();
let Value::Tagged(outer) = &v else {
panic!("expected outer Tagged, got {:?}", v);
};
assert_eq!(outer.tag().as_str(), "!shape");
let Value::Sequence(seq) = outer.value() else {
panic!("expected inner Sequence, got {:?}", outer.value());
};
assert_eq!(seq.len(), 2);
let Value::Tagged(item0) = &seq[0] else {
panic!("expected sequence item 0 Tagged, got {:?}", seq[0]);
};
assert_eq!(item0.tag().as_str(), "!circle");
assert_eq!(item0.value().as_str(), Some("1"));
let Value::Tagged(item1) = &seq[1] else {
panic!("expected sequence item 1 Tagged, got {:?}", seq[1]);
};
assert_eq!(item1.tag().as_str(), "!line");
}
#[test]
fn nested_tagged_block_mapping_inside_tagged_sequence() {
let yaml = "!shape\n- !circle\n center: 1\n radius: 7\n";
let v: Value = from_str(yaml).unwrap();
let Value::Tagged(outer) = &v else { panic!() };
let Value::Sequence(seq) = outer.value() else {
panic!()
};
let Value::Tagged(item) = &seq[0] else {
panic!("inner Tagged collapsed: got {:?}", seq[0]);
};
assert_eq!(item.tag().as_str(), "!circle");
let Value::Mapping(m) = item.value() else {
panic!()
};
assert_eq!(m.get("center").and_then(Value::as_i64), Some(1));
assert_eq!(m.get("radius").and_then(Value::as_i64), Some(7));
}
#[test]
fn to_string_value_with_config_emits_tag() {
use noyalib::SerializerConfig;
let v = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!Custom"),
Value::String("hello".into()),
)));
let cfg = SerializerConfig::default();
let s = noyalib::to_string_value_with_config(&v, &cfg).unwrap();
assert!(s.contains("!Custom"), "tag survived: {}", s);
assert!(s.contains("hello"), "inner survived: {}", s);
}
#[test]
fn to_writer_value_round_trip() {
use noyalib::SerializerConfig;
let v = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!Color"),
Value::String("#ff8800".into()),
)));
let mut buf: Vec<u8> = Vec::new();
noyalib::to_writer_value(&mut buf, &v).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("!Color"), "tag emitted: {}", s);
let mut buf2: Vec<u8> = Vec::new();
let cfg = SerializerConfig::default();
noyalib::to_writer_value_with_config(&mut buf2, &v, &cfg).unwrap();
let s2 = String::from_utf8(buf2).unwrap();
assert!(s2.contains("!Color"));
let v2: Value = from_str(&s).unwrap();
let Value::Tagged(t) = &v2 else { panic!() };
assert_eq!(t.tag().as_str(), "!Color");
}