#![allow(clippy::unwrap_used)]
use std::sync::Arc;
use noyalib::{Error, ParserConfig, TagRegistry, Value, from_str_with_config};
use serde_core::Deserialize as _;
fn cfg(tags: &[&str]) -> ParserConfig {
let mut reg = TagRegistry::new();
for t in tags {
let _ = reg.register(*t);
}
ParserConfig::new().tag_registry(Arc::new(reg))
}
#[derive(Debug, serde::Deserialize, PartialEq)]
struct Celsius(f64);
#[test]
fn scalar_tag_registered_deserializes_to_newtype() {
let cfg = cfg(&["!Celsius"]);
let c: Celsius = from_str_with_config("!Celsius 42.5", &cfg).unwrap();
assert_eq!(c, Celsius(42.5));
}
#[test]
fn scalar_tag_not_registered_errors_on_newtype_without_registry() {
let cfg = ParserConfig::new();
let res: Result<Celsius, _> = from_str_with_config("!Celsius 42.5", &cfg);
assert!(res.is_err(), "expected error without registry, got {res:?}");
}
#[test]
fn multiple_tags_in_registry() {
#[derive(Debug, serde::Deserialize, PartialEq)]
struct Meters(f64);
#[derive(Debug, serde::Deserialize, PartialEq)]
struct Seconds(f64);
let cfg = cfg(&["!Meters", "!Seconds"]);
let m: Meters = from_str_with_config("!Meters 10.0", &cfg).unwrap();
let s: Seconds = from_str_with_config("!Seconds 2.5", &cfg).unwrap();
assert_eq!(m, Meters(10.0));
assert_eq!(s, Seconds(2.5));
}
#[test]
fn registered_tag_on_sequence_strips_and_deserializes() {
let cfg = cfg(&["!MyVec"]);
let v: Vec<i32> = from_str_with_config("!MyVec [1, 2, 3]", &cfg).unwrap();
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn registered_tag_on_mapping_strips_and_deserializes() {
#[derive(Debug, serde::Deserialize, PartialEq)]
struct Point {
x: i32,
y: i32,
}
let cfg = cfg(&["!Point"]);
let p: Point = from_str_with_config("!Point {x: 1, y: 2}", &cfg).unwrap();
assert_eq!(p, Point { x: 1, y: 2 });
}
#[test]
fn unregistered_tag_on_scalar_falls_back_to_string() {
let cfg = ParserConfig::new();
let v: Value = from_str_with_config("!Other 42", &cfg).unwrap();
let Value::Tagged(t) = &v else {
panic!("expected Tagged, got {v:?}");
};
assert_eq!(t.tag().as_str(), "!Other");
assert_eq!(t.value().as_str(), Some("42"));
}
#[test]
fn core_tags_unaffected_by_registry() {
let cfg = cfg(&["!!str", "!!int"]);
let s: String = from_str_with_config("!!str 42", &cfg).unwrap();
assert_eq!(s, "42");
}
#[test]
fn empty_registry_is_no_op() {
let cfg = ParserConfig::new().tag_registry(Arc::new(TagRegistry::new()));
let v: Value = from_str_with_config("!Other 42", &cfg).unwrap();
let Value::Tagged(t) = &v else {
panic!("expected Tagged, got {v:?}");
};
assert_eq!(t.tag().as_str(), "!Other");
assert_eq!(t.value().as_str(), Some("42"));
}
#[test]
fn arc_registry_shares_across_configs() {
let reg = Arc::new(TagRegistry::new().with("!Celsius"));
let cfg_a = ParserConfig::new().tag_registry(Arc::clone(®));
let cfg_b = ParserConfig::strict().tag_registry(Arc::clone(®));
let a: Celsius = from_str_with_config("!Celsius 1.0", &cfg_a).unwrap();
let b: Celsius = from_str_with_config("!Celsius 2.0", &cfg_b).unwrap();
assert_eq!(a, Celsius(1.0));
assert_eq!(b, Celsius(2.0));
}
#[test]
fn streaming_deserializer_with_tag_registry_direct() {
use noyalib::StreamingDeserializer;
let reg = Arc::new(TagRegistry::new().with("!Celsius"));
let mut de = StreamingDeserializer::new("!Celsius 98.6").with_tag_registry(reg);
let c = Celsius::deserialize(&mut de).unwrap();
assert_eq!(c, Celsius(98.6));
}
#[test]
fn registry_contains_matches_registered() {
let reg = TagRegistry::new().with("!a").with("!b");
assert!(reg.contains("!a"));
assert!(reg.contains("!b"));
assert!(!reg.contains("!c"));
assert_eq!(reg.len(), 2);
assert!(!reg.is_empty());
}
#[test]
fn value_target_with_registry_detects_distinct_typed_key_collision() {
let cfg = cfg(&["!Celsius"]);
let res: Result<Value, _> = from_str_with_config("1: a\n\"1\": b\n", &cfg);
assert!(
matches!(res, Err(Error::KeyCollision(_))),
"Value+registry must detect the 1 vs \"1\" collision, got {res:?}"
);
let res2: Result<Value, _> = from_str_with_config("1: a\n\"1\": b\n", &ParserConfig::new());
assert!(matches!(res2, Err(Error::KeyCollision(_))));
}
#[test]
fn value_target_registry_strip_is_style_correct() {
let cfg = cfg(&["!Celsius"]);
let v: Value = from_str_with_config("!Celsius 42", &cfg).unwrap();
assert_eq!(v.as_i64(), Some(42));
let v: Value = from_str_with_config("!Celsius \"42\"", &cfg).unwrap();
assert_eq!(v.as_str(), Some("42"));
let v: Value = from_str_with_config("!Celsius 42", &ParserConfig::new()).unwrap();
assert!(
v.as_tagged().is_some(),
"unregistered tag must stay tagged, got {v:?}"
);
}
#[test]
fn value_target_registry_strips_collection_tag() {
let cfg = cfg(&["!Config"]);
let v: Value = from_str_with_config("!Config\na: 1\nb: 2\n", &cfg).unwrap();
assert!(
v.as_tagged().is_none(),
"registered collection tag must be stripped, got {v:?}"
);
assert_eq!(v["a"].as_i64(), Some(1));
assert_eq!(v["b"].as_i64(), Some(2));
}
#[test]
fn borrowing_entry_attaches_tag_registry() {
let cfg = cfg(&["!Celsius"]);
let c: Celsius = noyalib::from_str_borrowing_with_config("!Celsius 42.5", &cfg).unwrap();
assert_eq!(c, Celsius(42.5));
}
#[test]
fn borrowing_entry_without_registry_keeps_tag_opaque() {
let res: Result<Celsius, Error> =
noyalib::from_str_borrowing_with_config("!Celsius 42.5", &ParserConfig::new());
assert!(res.is_err(), "unregistered tag must not strip: {res:?}");
}