#![allow(missing_docs)]
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use noyalib::{
ParserConfig, Spanned, StreamingDeserializer, TagRegistry, Value, from_str,
from_str_with_config,
};
use serde::Deserialize;
use serde_bytes::ByteBuf;
#[test]
fn r3_peek_parser_event_populated_via_two_merges() {
let yaml = "\
a: &a {x: 1}
b: &b {y: 2}
target1:
<<: *a
z: 3
target2:
<<: *b
z: 4
";
let v: BTreeMap<String, BTreeMap<String, i64>> = from_str(yaml).unwrap();
assert_eq!(v["target1"]["x"], 1);
assert_eq!(v["target1"]["z"], 3);
assert_eq!(v["target2"]["y"], 2);
assert_eq!(v["target2"]["z"], 4);
}
#[test]
fn r3_next_parser_event_take_cached_via_merge() {
let yaml = "\
a: &a {x: 1}
target:
<<: *a
y: 2
";
let v: BTreeMap<String, BTreeMap<String, i64>> = from_str(yaml).unwrap();
assert_eq!(v["target"]["x"], 1);
assert_eq!(v["target"]["y"], 2);
}
#[test]
fn r3_streaming_de_unit_top_level_null() {
let mut de = StreamingDeserializer::new("~\n");
let _: () = Deserialize::deserialize(&mut de).unwrap();
}
#[test]
fn r3_streaming_de_with_explicit_doc_start() {
let mut de = StreamingDeserializer::new("---\n42\n");
let n: i64 = Deserialize::deserialize(&mut de).unwrap();
assert_eq!(n, 42);
}
#[test]
fn r3_skip_value_top_level_scalar_balance_zero() {
use serde::de::IgnoredAny;
let _: IgnoredAny = from_str("42\n").unwrap();
}
#[test]
fn r3_skip_value_top_level_alias_via_anchor() {
use serde::de::IgnoredAny;
let _: IgnoredAny = from_str("a: &a 1\nb: *a\n").unwrap();
}
#[test]
fn r3_streaming_de_empty_input_errors() {
let r: Result<i64, _> = from_str("");
let _ = r; }
#[test]
fn r3_restore_tag_to_seq_event_for_str_target() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct D {
s: String,
}
let yaml = "s: !MyTag [1, 2]\n";
let r: Result<D, _> = from_str(yaml);
assert!(r.is_err());
}
#[test]
fn r3_bool_from_sequence_errors() {
let r: Result<bool, _> = from_str("[1, 2, 3]\n");
assert!(r.is_err());
}
#[test]
fn r3_i64_from_sequence_errors() {
let r: Result<i64, _> = from_str("[1, 2]\n");
assert!(r.is_err());
}
#[test]
fn r3_u64_from_sequence_errors() {
let r: Result<u64, _> = from_str("[1, 2]\n");
assert!(r.is_err());
}
#[test]
fn r3_u64_rejects_negative_float() {
let r: Result<u64, _> = from_str("-1.5\n");
assert!(r.is_err());
}
#[test]
fn r3_f64_from_sequence_errors() {
let r: Result<f64, _> = from_str("[1.0]\n");
assert!(r.is_err());
}
#[test]
fn r3_string_field_rejects_plain_int() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct D {
s: String,
}
let r: Result<D, _> = from_str("s: 42\n");
assert!(r.is_err());
}
#[test]
fn r3_string_field_rejects_plain_bool() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct D {
s: String,
}
let r: Result<D, _> = from_str("s: true\n");
assert!(r.is_err());
}
#[test]
fn r3_string_field_rejects_plain_float() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct D {
s: String,
}
let r: Result<D, _> = from_str("s: 3.14\n");
assert!(r.is_err());
}
#[test]
fn r3_string_field_rejects_plain_null() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct D {
s: String,
}
let r: Result<D, _> = from_str("s: ~\n");
assert!(r.is_err());
}
#[test]
fn r3_string_target_with_top_level_quoted_passes() {
let s: String = from_str("'hello world'\n").unwrap();
assert_eq!(s, "hello world");
}
#[test]
fn r3_option_some_seq_inner() {
#[derive(Deserialize)]
struct D {
x: Option<Vec<i32>>,
}
let d: D = from_str("x: [1, 2, 3]\n").unwrap();
assert_eq!(d.x, Some(vec![1, 2, 3]));
}
#[test]
fn r3_option_some_map_inner() {
#[derive(Deserialize)]
struct D {
x: Option<BTreeMap<String, i32>>,
}
let d: D = from_str("x:\n a: 1\n").unwrap();
let inner = d.x.unwrap();
assert_eq!(inner["a"], 1);
}
#[test]
fn r3_option_top_level_none_via_quoted_null_string() {
let v: Option<String> = from_str("\"null\"\n").unwrap();
assert_eq!(v, Some("null".into()));
}
#[test]
fn r3_unit_top_level_via_streaming_de() {
let mut de = StreamingDeserializer::new("null\n");
let _: () = Deserialize::deserialize(&mut de).unwrap();
}
#[test]
fn r3_unit_struct_top_level() {
#[derive(Deserialize)]
struct U;
let _: U = from_str("~\n").unwrap();
}
#[test]
fn r3_newtype_with_core_null_tag() {
#[derive(Deserialize, Debug, PartialEq)]
struct N(Option<String>);
let n: N = from_str("!!null ~\n").unwrap();
assert_eq!(n, N(None));
}
#[test]
fn r3_newtype_with_core_bool_tag() {
#[derive(Deserialize, Debug, PartialEq)]
struct N(bool);
let n: N = from_str("!!bool true\n").unwrap();
assert_eq!(n, N(true));
}
#[test]
fn r3_newtype_with_core_float_tag() {
#[derive(Deserialize, Debug, PartialEq)]
struct N(f64);
let n: N = from_str("!!float 1.5\n").unwrap();
assert!((n.0 - 1.5).abs() < 1e-9);
}
#[test]
fn r3_newtype_with_core_seq_tag() {
#[derive(Deserialize, Debug, PartialEq)]
struct N(Vec<i64>);
let n: N = from_str("!!seq [1, 2]\n").unwrap();
assert_eq!(n, N(vec![1, 2]));
}
#[test]
fn r3_newtype_with_core_map_tag() {
#[derive(Deserialize, Debug, PartialEq)]
struct N(BTreeMap<String, i64>);
let n: N = from_str("!!map {a: 1}\n").unwrap();
assert_eq!(n.0["a"], 1);
}
#[test]
fn r3_seq_target_sees_mapping_event_errors() {
let r: Result<Vec<i64>, _> = from_str("a: 1\nb: 2\n");
assert!(r.is_err());
}
#[test]
fn r3_map_target_sees_sequence_event_errors() {
let r: Result<BTreeMap<String, i64>, _> = from_str("[1, 2]\n");
assert!(r.is_err());
}
#[test]
fn r3_enum_mapping_with_non_scalar_variant_name_errors() {
#[derive(Debug, Deserialize, PartialEq)]
#[allow(dead_code)]
enum E {
A(i32),
}
let yaml = "[1, 2]:\n - 1\n";
let r: Result<E, _> = from_str(yaml);
assert!(r.is_err());
}
#[derive(Debug, Deserialize, PartialEq)]
enum E2 {
Unit,
Wrap(i32),
Pair(i32, i32),
S { a: i32 },
}
#[test]
fn r3_streaming_unit_variant_via_mapping_form() {
let yaml = "Unit: ~\n";
let e: E2 = from_str(yaml).unwrap();
assert_eq!(e, E2::Unit);
}
#[test]
fn r3_streaming_newtype_variant_value() {
let yaml = "Wrap: 7\n";
let e: E2 = from_str(yaml).unwrap();
assert_eq!(e, E2::Wrap(7));
}
#[test]
fn r3_streaming_tuple_variant_value() {
let yaml = "Pair: [1, 2]\n";
let e: E2 = from_str(yaml).unwrap();
assert_eq!(e, E2::Pair(1, 2));
}
#[test]
fn r3_streaming_struct_variant_value() {
let yaml = "S: {a: 5}\n";
let e: E2 = from_str(yaml).unwrap();
assert_eq!(e, E2::S { a: 5 });
}
#[test]
fn r3_identifier_via_struct_field_name() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct D {
alpha: i32,
beta: i32,
}
let d: D = from_str("alpha: 1\nbeta: 2\n").unwrap();
let _ = d;
}
#[test]
fn r3_ignored_any_skips_seq_inside_map() {
use serde::de::IgnoredAny;
#[derive(Deserialize)]
#[allow(dead_code)]
struct D {
keep: i32,
#[serde(default)]
skip_me: IgnoredAny,
}
let yaml = "keep: 1\nskip_me: [1, 2, 3, [4, [5, [6]]]]\n";
let d: D = from_str(yaml).unwrap();
assert_eq!(d.keep, 1);
}
#[test]
fn r3_bytes_top_level_string_passes() {
let b: ByteBuf = from_str("'raw'\n").unwrap();
assert_eq!(b.as_ref(), b"raw");
}
#[test]
fn r3_bytes_top_level_int_errors() {
let r: Result<ByteBuf, _> = from_str("42\n");
assert!(r.is_err());
}
#[test]
fn r3_bytes_top_level_bool_errors() {
let r: Result<ByteBuf, _> = from_str("true\n");
assert!(r.is_err());
}
#[test]
fn r3_bytes_top_level_null_errors() {
let r: Result<ByteBuf, _> = from_str("~\n");
assert!(r.is_err());
}
#[test]
fn r3_bytes_top_level_float_errors() {
let r: Result<ByteBuf, _> = from_str("3.14\n");
assert!(r.is_err());
}
#[test]
fn r3_bytes_top_level_sequence_errors() {
let r: Result<ByteBuf, _> = from_str("[1, 2]\n");
assert!(r.is_err());
}
#[test]
fn r3_bytes_binary_tag_with_mapping_event_errors() {
let yaml = "!!binary\n a: b\n";
let r: Result<ByteBuf, _> = from_str(yaml);
let _ = r; }
#[test]
fn r3_seq_with_two_elements_terminates() {
let v: Vec<i64> = from_str("[1, 2]\n").unwrap();
assert_eq!(v, vec![1, 2]);
}
#[test]
fn r3_map_with_three_keys_terminates() {
let m: BTreeMap<String, i64> = from_str("a: 1\nb: 2\nc: 3\n").unwrap();
assert_eq!(m.len(), 3);
}
#[test]
fn r3_tag_map_access_for_unregistered_bang_handle() {
let yaml = "!Custom 42\n";
let v: Value = from_str(yaml).unwrap();
assert!(v.is_tagged() || v.is_mapping() || v.is_i64());
}
#[test]
fn r3_tag_map_access_for_unregistered_double_bang_handle() {
let yaml = "!!my/Custom 42\n";
let v: Value = from_str(yaml).unwrap();
let _ = v;
}
#[test]
fn r3_tag_enum_access_unit_variant_dispatch() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
#[serde(rename = "!Tag")]
Tag,
}
let yaml = "!Tag\n";
let r: Result<E, _> = from_str(yaml);
let _ = r; }
#[test]
fn r3_tag_enum_access_newtype_variant_dispatch() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
#[serde(rename = "!Wrap")]
Wrap(i32),
}
let yaml = "!Wrap 42\n";
let r: Result<E, _> = from_str(yaml);
let _ = r;
}
#[test]
fn r3_tag_enum_access_struct_variant_dispatch() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
#[serde(rename = "!S")]
S { a: i32 },
}
let yaml = "!S {a: 1}\n";
let r: Result<E, _> = from_str(yaml);
let _ = r;
}
#[test]
fn r3_tag_enum_access_tuple_variant_dispatch() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
#[serde(rename = "!P")]
P(i32, i32),
}
let yaml = "!P [1, 2]\n";
let r: Result<E, _> = from_str(yaml);
let _ = r;
}
#[test]
fn r3_sexagesimal_int_single_part_returns_none() {
let cfg = ParserConfig::new().legacy_sexagesimal(true);
let n: i64 = from_str_with_config("30\n", &cfg).unwrap();
assert_eq!(n, 30);
}
#[test]
fn r3_sexagesimal_int_with_overflow_returns_none() {
let yaml = "999999999999:00:00:00:00:00\n";
let cfg = ParserConfig::new().legacy_sexagesimal(true);
let m: Value = from_str_with_config(yaml, &cfg).unwrap();
let _ = m; }
#[test]
fn r3_sexagesimal_float_invalid_returns_none() {
let yaml = "x: ab:cd.5\n";
let cfg = ParserConfig::new().legacy_sexagesimal(true);
let m: BTreeMap<String, String> = from_str_with_config(yaml, &cfg).unwrap();
assert_eq!(m["x"], "ab:cd.5");
}
#[test]
fn r3_sexagesimal_float_overflow_component_returns_none() {
let yaml = "x: 1:99.5\n";
let cfg = ParserConfig::new().legacy_sexagesimal(true);
let m: BTreeMap<String, String> = from_str_with_config(yaml, &cfg).unwrap();
assert_eq!(m["x"], "1:99.5");
}
#[test]
fn r3_sexagesimal_float_with_positive_sign() {
let yaml = "+1:30.5\n";
let cfg = ParserConfig::new().legacy_sexagesimal(true);
let f: f64 = from_str_with_config(yaml, &cfg).unwrap();
assert!((f - 90.5).abs() < 1e-9);
}
#[test]
fn r3_parse_integer_uppercase_hex() {
let n: i64 = from_str("0X10\n").unwrap();
assert_eq!(n, 16);
}
#[test]
fn r3_parse_integer_uppercase_octal() {
let n: i64 = from_str("0O17\n").unwrap();
assert_eq!(n, 15);
}
#[test]
fn r3_parse_integer_sign_only_returns_none() {
let s: String = from_str("\"+\"\n").unwrap();
assert_eq!(s, "+");
}
#[test]
fn r3_parse_integer_legacy_octal_with_zero_only() {
let cfg = ParserConfig::new().legacy_octal_numbers(true);
let n: i64 = from_str_with_config("0\n", &cfg).unwrap();
assert_eq!(n, 0);
}
#[test]
fn r3_parse_integer_legacy_octal_with_8_falls_through() {
let cfg = ParserConfig::new().legacy_octal_numbers(true);
let n: i64 = from_str_with_config("08\n", &cfg).unwrap();
assert_eq!(n, 8);
}
#[test]
fn r3_merge_source_with_complex_key_falls_back() {
let yaml = "\
base: &b
? [a, b]
: v
target:
<<: *b
k: 1
";
let r: Result<Value, _> = from_str(yaml);
let _ = r; }
#[test]
fn r3_merge_source_with_map_then_alias_value() {
let yaml = "\
shared: &s [1, 2]
base: &b
inner_map:
deep: 1
alias_field: *s
target:
<<: *b
";
let v: Value = from_str(yaml).unwrap();
let t = v.get_path("target").unwrap();
assert!(t.get_path("inner_map").is_some());
}
#[test]
fn r3_extract_local_keys_two_aliases_in_local_tail() {
let yaml = "\
s1: &s1 v1
s2: &s2 v2
base: &b {a: 1}
target:
<<: *b
c: *s1
d: *s2
";
let v: Value = from_str(yaml).unwrap();
let t = v.get_path("target").unwrap();
assert_eq!(t.get_path("c").and_then(|x| x.as_str()), Some("v1"));
assert_eq!(t.get_path("d").and_then(|x| x.as_str()), Some("v2"));
}
#[test]
fn r3_extract_local_keys_map_value_then_more_keys() {
let yaml = "\
base: &b {a: 1}
target:
<<: *b
k1:
inner: 1
k2: literal
k3: 9
";
let v: Value = from_str(yaml).unwrap();
let t = v.get_path("target").unwrap();
assert!(t.get_path("k1").map(|x| x.is_mapping()).unwrap_or(false));
assert_eq!(t.get_path("k2").and_then(|x| x.as_str()), Some("literal"));
assert_eq!(t.get_path("k3").and_then(|x| x.as_i64()), Some(9));
}
#[test]
fn r3_multi_source_merge_local_overrides_later_source() {
let yaml = "\
a: &a {k1: from_a, k2: from_a}
b: &b {k1: from_b, k3: from_b}
target:
<<: [*a, *b]
k1: from_local
";
let v: BTreeMap<String, BTreeMap<String, String>> = from_str(yaml).unwrap();
let t = &v["target"];
assert_eq!(t["k1"], "from_local");
assert_eq!(t["k2"], "from_a");
assert_eq!(t["k3"], "from_b");
}
#[test]
fn r3_spanned_map_shape() {
#[derive(Deserialize)]
struct D {
m: Spanned<BTreeMap<String, i32>>,
}
let yaml = "m:\n a: 1\n b: 2\n";
let d: D = from_str(yaml).unwrap();
assert_eq!(d.m.value["a"], 1);
}
#[test]
fn r3_vec_of_spanned() {
let v: Vec<Spanned<i32>> = from_str("[1, 2, 3]\n").unwrap();
assert_eq!(v.len(), 3);
assert_eq!(v[0].value, 1);
}
#[test]
fn r3_streaming_de_direct_seq_consumption() {
let mut de = StreamingDeserializer::new("[1, 2, 3]\n");
let v: Vec<i64> = Deserialize::deserialize(&mut de).unwrap();
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn r3_streaming_de_direct_map_consumption() {
let mut de = StreamingDeserializer::new("a: 1\nb: 2\n");
let m: HashMap<String, i64> = Deserialize::deserialize(&mut de).unwrap();
assert_eq!(m["a"], 1);
assert_eq!(m["b"], 2);
}
#[test]
fn r3_streaming_de_with_config_tight_limits() {
let cfg = ParserConfig::new().max_alias_expansions(10);
let mut de = StreamingDeserializer::with_config("k: 1\n", &cfg);
let m: BTreeMap<String, i64> = Deserialize::deserialize(&mut de).unwrap();
assert_eq!(m["k"], 1);
}
#[test]
fn r3_streaming_de_with_registry_strips_custom_tag() {
let registry = Arc::new(TagRegistry::new().with("!Custom"));
let mut de = StreamingDeserializer::new("!Custom 42\n").with_tag_registry(registry);
let n: i64 = Deserialize::deserialize(&mut de).unwrap();
assert_eq!(n, 42);
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "kind")]
enum InternallyTagged {
First { x: i32 },
Second { y: String },
}
#[test]
fn r3_internally_tagged_first() {
let yaml = "kind: First\nx: 1\n";
let e: InternallyTagged = from_str(yaml).unwrap();
assert_eq!(e, InternallyTagged::First { x: 1 });
}
#[test]
fn r3_internally_tagged_second() {
let yaml = "kind: Second\ny: hello\n";
let e: InternallyTagged = from_str(yaml).unwrap();
assert_eq!(e, InternallyTagged::Second { y: "hello".into() });
}
#[test]
fn r3_load_all_as_two_docs_typed() {
let docs: Vec<i32> = noyalib::load_all_as("---\n1\n---\n2\n").unwrap();
assert_eq!(docs, vec![1, 2]);
}
#[test]
fn r3_duplicate_first_with_complex_dropped_value() {
use noyalib::DuplicateKeyPolicy;
let yaml = "\
k: 1
k:
nested:
- 1
- 2
k:
- x
- y
";
let cfg = ParserConfig::new().duplicate_key_policy(DuplicateKeyPolicy::First);
let m: BTreeMap<String, Value> = from_str_with_config(yaml, &cfg).unwrap();
assert_eq!(m["k"].as_i64(), Some(1));
}
#[test]
fn r3_top_level_empty_mapping_value() {
let m: BTreeMap<String, BTreeMap<String, i64>> = from_str("k: {}\n").unwrap();
assert!(m["k"].is_empty());
}
#[test]
fn r3_top_level_empty_sequence_value() {
let m: BTreeMap<String, Vec<i64>> = from_str("k: []\n").unwrap();
assert!(m["k"].is_empty());
}
#[test]
fn r3_explicit_doc_marker_with_empty_value() {
let v: Option<i32> = from_str("--- ~\n").unwrap();
assert_eq!(v, None);
}
#[test]
fn r3_merge_three_sources_with_overlapping_keys() {
let yaml = "\
a: &a {x: 1, y: 1}
b: &b {y: 2, z: 2}
c: &c {z: 3, w: 3}
target:
<<: [*a, *b, *c]
";
let m: BTreeMap<String, BTreeMap<String, i64>> = from_str(yaml).unwrap();
let t = &m["target"];
assert_eq!(t["x"], 1);
assert_eq!(t["y"], 1);
assert_eq!(t["z"], 2);
assert_eq!(t["w"], 3);
}
#[test]
fn r3_resolve_plain_inf_uppercase() {
let f: f64 = from_str(".INF\n").unwrap();
assert!(f.is_infinite() && f.is_sign_positive());
}
#[test]
fn r3_resolve_plain_neg_inf() {
let f: f64 = from_str("-.INF\n").unwrap();
assert!(f.is_infinite() && f.is_sign_negative());
}
#[test]
fn r3_resolve_plain_nan() {
let f: f64 = from_str(".NAN\n").unwrap();
assert!(f.is_nan());
}
#[test]
fn r3_resolve_plain_yes_no_legacy() {
let cfg = ParserConfig::new().legacy_booleans(true);
let b: bool = from_str_with_config("yes\n", &cfg).unwrap();
assert!(b);
let b: bool = from_str_with_config("NO\n", &cfg).unwrap();
assert!(!b);
}
#[test]
fn r3_resolve_plain_on_off_legacy_non_strict() {
let cfg = ParserConfig::new().legacy_booleans(true);
let b: bool = from_str_with_config("on\n", &cfg).unwrap();
assert!(b);
let b: bool = from_str_with_config("off\n", &cfg).unwrap();
assert!(!b);
}
#[test]
fn r3_resolve_plain_strict_rejects_uppercase_true() {
let cfg = ParserConfig::new().strict_booleans(true);
let r: Result<bool, _> = from_str_with_config("True\n", &cfg);
assert!(r.is_err());
}
#[test]
fn r3_no_schema_keeps_int_as_string() {
let cfg = ParserConfig::new().no_schema(true);
let s: String = from_str_with_config("42\n", &cfg).unwrap();
assert_eq!(s, "42");
}
#[test]
fn r3_no_schema_keeps_bool_as_string() {
let cfg = ParserConfig::new().no_schema(true);
let s: String = from_str_with_config("true\n", &cfg).unwrap();
assert_eq!(s, "true");
}