#![allow(missing_docs)]
use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;
use noyalib::{
ParserConfig, Spanned, Tag, TagRegistry, TaggedValue, Value, from_str, from_str_with_config,
from_value,
};
use serde::Deserialize;
#[derive(Debug)]
struct AlwaysReject;
impl noyalib::policy::Policy for AlwaysReject {
fn check_value(&self, _value: &Value) -> noyalib::Result<()> {
Err(noyalib::Error::Deserialize("rejected".into()))
}
}
#[test]
fn final_de_value_fastpath_policy_check_value_rejects() {
let cfg = ParserConfig::new().with_policy(AlwaysReject);
let res: Result<Value, _> = from_str_with_config("k: v\n", &cfg);
assert!(res.is_err());
}
#[test]
fn final_de_typed_ast_policy_check_value_rejects() {
let cfg = ParserConfig::new().with_policy(AlwaysReject);
let res: Result<BTreeMap<String, String>, _> = from_str_with_config("k: v\n", &cfg);
assert!(res.is_err());
}
struct FailingReader;
impl std::io::Read for FailingReader {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("simulated io fail"))
}
}
#[test]
fn final_de_from_reader_with_config_io_error() {
let cfg = ParserConfig::default();
let res: Result<i64, _> = noyalib::from_reader_with_config(FailingReader, &cfg);
assert!(res.is_err());
}
#[test]
fn final_de_nested_tagged_via_from_value() {
let inner = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!Inner"),
Value::String("nested".into()),
)));
let outer = Value::Tagged(Box::new(TaggedValue::new(Tag::new("!Outer"), inner)));
let v: Value = from_value(&outer).expect("nested tagged round-trip");
let outer_tag = v.as_tagged().expect("outer is tagged");
assert_eq!(outer_tag.tag().as_str(), "!Outer");
let inner_tag = outer_tag.value().as_tagged().expect("inner is tagged");
assert_eq!(inner_tag.tag().as_str(), "!Inner");
}
#[derive(Debug)]
struct NoOpPolicy;
impl noyalib::policy::Policy for NoOpPolicy {}
#[test]
fn final_de_wrap_err_attaches_location_when_span_present() {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Doc {
b: serde_bytes::ByteBuf,
}
let cfg = ParserConfig::new().with_policy(NoOpPolicy);
let yaml = "b: !!binary \"@@@@invalid_base64@@@@\"\n";
let res: Result<Doc, _> = from_str_with_config(yaml, &cfg);
let err = res.expect_err("base64 decode error");
let msg = err.to_string();
assert!(!msg.is_empty());
}
#[test]
fn final_de_deserialize_any_bool() {
let v = Value::Bool(true);
let out: Value = from_value(&v).expect("bool");
assert_eq!(out.as_bool(), Some(true));
}
#[test]
fn final_de_deserialize_any_float() {
let v = Value::from(2.5_f64);
let out: Value = from_value(&v).expect("float");
assert!(out.as_f64().is_some());
}
#[test]
fn final_de_deserialize_any_sequence() {
let v = Value::Sequence(vec![Value::from(1_i64), Value::from(2_i64)]);
let out: Value = from_value(&v).expect("seq");
assert!(out.is_sequence());
}
#[test]
fn final_de_deserialize_any_preserve_tags_visits_map() {
let yaml = "!MyTag\n k: v\n";
let v: Value = from_str(yaml).expect("tagged-map parses");
assert!(v.is_tagged());
}
#[test]
fn final_de_deserialize_str_binary_tag_non_string_errors() {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct S {
s: String,
}
let v = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!!binary"),
Value::Sequence(vec![Value::from(1_i64)]),
)));
let mut m = noyalib::Mapping::new();
let _ = m.insert("s", v);
let outer = Value::Mapping(m);
let r: Result<S, _> = from_value(&outer);
let _ = r; let yaml = "s: !!binary\n - 1\n";
let cfg = ParserConfig::default().ignore_binary_tag_for_string(true);
let r2: Result<S, _> = from_str_with_config(yaml, &cfg);
assert!(r2.is_err());
}
#[test]
fn final_de_deserialize_bytes_invalid_base64() {
let v = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!!binary"),
Value::String("@@not-valid-b64@@".into()),
)));
let r: Result<serde_bytes::ByteBuf, _> = from_value(&v);
assert!(r.is_err());
}
#[test]
fn final_de_spanned_through_ast_path() {
#[derive(Debug, Deserialize)]
struct Doc {
n: Spanned<i32>,
}
let cfg = ParserConfig::new().with_policy(NoOpPolicy);
let yaml = "n: 42\n";
let d: Doc = from_str_with_config(yaml, &cfg).expect("spanned via ast path");
assert_eq!(d.n.value, 42);
assert!(d.n.start.line() >= 1);
}
#[test]
fn final_de_variant_seed_unknown_variant_errors() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
A,
B,
}
let cfg = ParserConfig::new().with_policy(NoOpPolicy);
let yaml = "C\n"; let res: Result<E, _> = from_str_with_config(yaml, &cfg);
assert!(res.is_err());
}
#[derive(Debug, Deserialize, PartialEq)]
enum Choice {
A,
#[allow(dead_code)]
B(i32),
#[allow(dead_code)]
Two(i32, i32),
#[allow(dead_code)]
Strukt {
x: i32,
},
}
#[test]
fn final_de_unit_variant_with_span_ctx() {
let cfg = ParserConfig::new().with_policy(NoOpPolicy);
let yaml = "A\n";
let c: Choice = from_str_with_config(yaml, &cfg).expect("unit variant with span ctx");
assert_eq!(c, Choice::A);
}
#[test]
fn final_de_newtype_variant_with_span_ctx() {
let cfg = ParserConfig::new().with_policy(NoOpPolicy);
let yaml = "B: 7\n";
let c: Choice = from_str_with_config(yaml, &cfg).expect("newtype variant with span ctx");
assert_eq!(c, Choice::B(7));
}
#[test]
fn final_de_tuple_variant_with_span_ctx() {
let cfg = ParserConfig::new().with_policy(NoOpPolicy);
let yaml = "Two: [1, 2]\n";
let c: Choice = from_str_with_config(yaml, &cfg).expect("tuple variant with span ctx");
assert_eq!(c, Choice::Two(1, 2));
}
#[test]
fn final_de_struct_variant_with_span_ctx() {
let cfg = ParserConfig::new().with_policy(NoOpPolicy);
let yaml = "Strukt:\n x: 5\n";
let c: Choice = from_str_with_config(yaml, &cfg).expect("struct variant with span ctx");
assert_eq!(c, Choice::Strukt { x: 5 });
}
#[cfg(feature = "figment")]
#[test]
fn final_de_figment_provider_with_spanned_field_uses_ast_fallback() {
use figment::Figment;
use figment::providers::Format as _;
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Cfg {
port: u16,
}
let yaml = "port: 8080\n";
let cfg: Cfg = Figment::new()
.merge(noyalib::figment::Yaml::string(yaml))
.extract()
.expect("figment extract");
assert_eq!(cfg.port, 8080);
}
#[test]
fn final_streaming_parse_error_in_peek_parser_event() {
let yaml = "k: { unclosed\n";
let res: Result<BTreeMap<String, String>, _> = from_str(yaml);
assert!(res.is_err());
}
#[test]
fn final_streaming_malformed_after_merge_alias() {
let yaml = "\
base: &b {a: 1}
target:
<<: *b
bad: { unclosed
";
let res: Result<BTreeMap<String, BTreeMap<String, Value>>, _> = from_str(yaml);
assert!(res.is_err());
}
#[test]
fn final_streaming_anchor_scalar_seq_map_typed_target() {
#[derive(Deserialize)]
struct Doc {
a: i64,
b: Vec<i64>,
d: i64,
e: Vec<i64>,
}
let yaml = "\
a: &x1 42
b: &x2 [1, 2]
d: *x1
e: *x2
";
let d: Doc = from_str(yaml).expect("typed anchored target");
assert_eq!(d.a, 42);
assert_eq!(d.b, vec![1, 2]);
assert_eq!(d.d, 42);
assert_eq!(d.e, vec![1, 2]);
}
#[test]
fn final_streaming_anchor_records_alias_typed_target() {
#[derive(Deserialize)]
struct Doc {
shared: i64,
copy: BTreeMap<String, i64>,
}
let yaml = "\
shared: &shared 42
container: &c
ref: *shared
copy: *c
";
let d: Doc = from_str(yaml).expect("alias-in-anchor typed");
assert_eq!(d.shared, 42);
assert_eq!(d.copy.get("ref"), Some(&42));
}
#[test]
fn final_streaming_alias_unknown_top_level() {
let yaml = "v: *missing\n";
let r: Result<BTreeMap<String, i64>, _> = from_str(yaml);
assert!(r.is_err());
}
#[test]
fn final_streaming_merge_two_aliases_sequence() {
let yaml = "\
a: &a {x: 1}
b: &b {y: 2}
target:
<<: [*a, *b]
z: 3
";
let v: BTreeMap<String, BTreeMap<String, i64>> = from_str(yaml).unwrap();
assert_eq!(v["target"]["x"], 1);
assert_eq!(v["target"]["y"], 2);
assert_eq!(v["target"]["z"], 3);
}
#[test]
fn final_streaming_merge_with_deep_local_tail() {
let yaml = "\
base: &b {k: 1}
target:
<<: *b
outer:
inner_map:
a: 1
b:
- 1
- 2
inner_seq:
- x
- {nested: deep}
trailing: 9
";
let v: Value = from_str(yaml).expect("deep tail parses");
let t = v.get_path("target").unwrap();
assert!(t.get_path("outer").is_some());
assert_eq!(t.get_path("trailing").and_then(|x| x.as_i64()), Some(9));
}
#[test]
fn final_streaming_skip_to_content_with_directives() {
let yaml = "%YAML 1.2\n---\nk: v\n";
let m: BTreeMap<String, String> = from_str(yaml).unwrap();
assert_eq!(m["k"], "v");
}
#[test]
fn final_streaming_ignored_any_skips_tagged_seq_in_map() {
#[derive(Deserialize)]
struct Small {
keep: i64,
}
let yaml = "\
keep: 7
extra: !Custom
- 1
- 2
- {a: 1}
";
let s: Small = from_str(yaml).unwrap();
assert_eq!(s.keep, 7);
}
#[test]
fn final_streaming_tagged_mapping_restore_tag() {
let yaml = "v: !Custom {a: 1, b: 2}\n";
let v: Value = from_str(yaml).expect("tagged mapping via ast");
let inner = v.get_path("v").unwrap();
assert!(inner.is_tagged() || inner.is_mapping());
}
#[test]
fn final_streaming_deserialize_any_seq_via_value_target() {
let yaml = "[1, 2, 3]\n";
let v: Value = from_str(yaml).expect("seq parses to value");
let s = v.as_sequence().unwrap();
assert_eq!(s.len(), 3);
}
#[test]
fn final_streaming_bool_happy_path() {
let b: bool = from_str("true\n").unwrap();
assert!(b);
}
#[test]
fn final_streaming_i64_happy() {
let n: i64 = from_str("42\n").unwrap();
assert_eq!(n, 42);
}
#[test]
fn final_streaming_u64_happy() {
let n: u64 = from_str("42\n").unwrap();
assert_eq!(n, 42);
}
#[test]
fn final_streaming_f64_happy() {
let f: f64 = from_str("2.5\n").unwrap();
assert!((f - 2.5).abs() < 1e-9);
}
#[test]
fn final_streaming_deserialize_str_mapping_event_errors() {
#[derive(Deserialize)]
#[allow(dead_code)]
struct Doc {
s: String,
}
let yaml = "s:\n k: v\n";
let res: Result<Doc, _> = from_str(yaml);
assert!(res.is_err());
}
#[test]
fn final_streaming_option_some_string() {
#[derive(Deserialize)]
struct D {
x: Option<String>,
}
let d: D = from_str("x: hello\n").unwrap();
assert_eq!(d.x, Some("hello".into()));
}
#[test]
fn final_streaming_unit_rejects_non_null() {
let r: Result<(), _> = from_str("42\n");
assert!(r.is_err());
}
#[test]
fn final_streaming_spanned_newtype_falls_back() {
#[derive(Deserialize)]
struct D {
v: Spanned<i32>,
}
let d: D = from_str("v: 7\n").unwrap();
assert_eq!(d.v.value, 7);
}
#[test]
fn final_streaming_newtype_with_core_str_tag() {
#[derive(Deserialize, PartialEq, Debug)]
struct Wrap(String);
#[derive(Deserialize)]
struct D {
v: Wrap,
}
let d: D = from_str("v: !!str hello\n").unwrap();
assert_eq!(d.v, Wrap("hello".into()));
}
#[test]
fn final_streaming_seq_empty_happy_path() {
let v: Vec<i64> = from_str("[]\n").unwrap();
assert!(v.is_empty());
}
#[test]
fn final_streaming_map_empty_happy_path() {
let m: BTreeMap<String, i64> = from_str("{}\n").unwrap();
assert!(m.is_empty());
}
#[test]
fn final_streaming_enum_via_unregistered_tag_dispatch() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
#[serde(rename = "!Tagged")]
Tagged(i32),
}
let yaml = "!Tagged 42\n";
let e: E = from_str(yaml).expect("tagged enum dispatch via TagEnumAccess");
assert_eq!(e, E::Tagged(42));
}
#[test]
fn final_streaming_enum_top_level_unit_scalar() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
Unit,
#[allow(dead_code)]
Wrap(i32),
}
let e: E = from_str("Unit\n").unwrap();
assert_eq!(e, E::Unit);
}
#[test]
fn final_streaming_enum_non_scalar_non_mapping_errors() {
#[derive(Debug, Deserialize, PartialEq)]
#[allow(dead_code)]
enum E {
Unit,
Pair(i32, i32),
}
let res: Result<E, _> = from_str("[1, 2, 3]\n");
assert!(res.is_err());
}
#[test]
fn final_streaming_identifier_mapping_value_errors() {
let res: Result<HashMap<String, i64>, _> = from_str("k1: 1\nk2: 2\n");
assert!(res.is_ok());
}
#[test]
fn final_streaming_bytes_binary_decode_success() {
#[derive(Deserialize)]
struct D {
b: serde_bytes::ByteBuf,
}
let yaml = "b: !!binary aGVsbG8=\n"; let d: D = from_str(yaml).unwrap();
assert_eq!(d.b.as_ref(), b"hello");
}
#[test]
fn final_streaming_bytes_from_quoted_string() {
#[derive(Deserialize)]
struct D {
b: serde_bytes::ByteBuf,
}
let yaml = "b: \"raw bytes\"\n";
let d: D = from_str(yaml).unwrap();
assert_eq!(d.b.as_ref(), b"raw bytes");
}
#[test]
fn final_streaming_seq_with_one_element_terminates_on_end() {
let v: Vec<i64> = from_str("[42]\n").unwrap();
assert_eq!(v, vec![42]);
}
#[test]
fn final_streaming_merge_seq_with_scalar_falls_back() {
let yaml = "\
a: &a {k: 1}
target:
<<:
- *a
- some_scalar
";
let r: Result<BTreeMap<String, BTreeMap<String, Value>>, _> = from_str(yaml);
let _ = r; }
#[test]
fn final_streaming_duplicate_first_keeps_first_three_collisions() {
use noyalib::DuplicateKeyPolicy;
let yaml = "k: 1\nk: 2\nk: 3\nk: 4\n";
let cfg = ParserConfig::new().duplicate_key_policy(DuplicateKeyPolicy::First);
let m: BTreeMap<String, i64> = from_str_with_config(yaml, &cfg).unwrap();
assert_eq!(m["k"], 1);
}
#[test]
fn final_streaming_tagged_enum_variant_access_unit() {
#[derive(Debug, Deserialize, PartialEq)]
enum E {
Plain,
#[allow(dead_code)]
Wrap(i32),
#[allow(dead_code)]
Pair(i32, i32),
#[allow(dead_code)]
S {
a: i32,
},
}
let registry = Arc::new(TagRegistry::new().with("!Wrap"));
let cfg = ParserConfig::default().tag_registry(Arc::clone(®istry));
let yaml = "Plain\n";
let e: E = from_str_with_config(yaml, &cfg).unwrap();
assert_eq!(e, E::Plain);
}
#[test]
fn final_streaming_unregistered_tag_enum_falls_back() {
let yaml = "v: !Custom 7\n";
let v: Value = from_str(yaml).expect("falls back to AST");
assert!(v.is_mapping());
}
#[test]
fn final_streaming_sexagesimal_positive_sign() {
let yaml = "+1:30\n";
let cfg = ParserConfig::new().legacy_sexagesimal(true);
let n: i64 = from_str_with_config(yaml, &cfg).unwrap();
assert_eq!(n, 90);
}
#[test]
fn final_streaming_sexagesimal_float_neg_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 final_streaming_sexagesimal_float_all_digit_parts() {
let yaml = "x: 0:30.25\n";
let cfg = ParserConfig::new().legacy_sexagesimal(true);
let m: BTreeMap<String, f64> = from_str_with_config(yaml, &cfg).unwrap();
assert!((m["x"] - 30.25).abs() < 1e-9);
}
#[test]
fn final_streaming_signed_decimal_integer() {
let n: i64 = from_str("-42\n").unwrap();
assert_eq!(n, -42);
}
#[test]
fn final_streaming_signed_decimal_with_plus() {
let n: i64 = from_str("+42\n").unwrap();
assert_eq!(n, 42);
}
#[test]
fn final_streaming_merge_collect_keys_with_seq_and_map_values() {
let yaml = "\
shared: &t target_val
base: &b
k_int: 42
k_seq: [1, 2, 3]
k_map: {nested: deep}
k_alias_value: *t
target:
<<: *b
k_local: from_local
";
let v: Value = from_str(yaml).expect("multi-shape merge body");
let t = v.get_path("target").unwrap();
assert_eq!(t.get_path("k_int").and_then(|x| x.as_i64()), Some(42));
}
#[test]
fn final_streaming_merge_nested_sequence_in_body() {
let yaml = "\
base: &b
k_outer: [[1, 2], [3, 4]]
k_str: hello
target:
<<: *b
";
let v: Value = from_str(yaml).expect("nested-seq body");
let t = v.get_path("target").unwrap();
assert!(
t.get_path("k_outer")
.map(|x| x.is_sequence())
.unwrap_or(false)
);
}
#[test]
fn final_streaming_extract_local_keys_with_alias_value() {
let yaml = "\
shared: &s shared_val
base: &b {a: 1, b: 2}
target:
<<: *b
c: *s
d: 4
";
let v: Value = from_str(yaml).expect("alias-as-local-tail-value");
let t = v.get_path("target").unwrap();
assert_eq!(t.get_path("a").and_then(|x| x.as_i64()), Some(1));
assert_eq!(t.get_path("b").and_then(|x| x.as_i64()), Some(2));
assert_eq!(t.get_path("c").and_then(|x| x.as_str()), Some("shared_val"));
assert_eq!(t.get_path("d").and_then(|x| x.as_i64()), Some(4));
}
#[test]
fn final_streaming_spanned_recursive_into_seq() {
#[derive(Debug, Deserialize)]
struct D {
items: Spanned<Vec<i32>>,
}
let yaml = "items: [1, 2, 3]\n";
let d: D = from_str(yaml).unwrap();
assert_eq!(d.items.value, vec![1, 2, 3]);
}
#[test]
fn final_streaming_spanned_option_none() {
#[derive(Debug, Deserialize)]
struct D {
x: Spanned<Option<i32>>,
}
let yaml = "x: ~\n";
let d: D = from_str(yaml).unwrap();
assert_eq!(d.x.value, None);
}
#[test]
fn final_streaming_hashmap_typed_target() {
let yaml = "a: 1\nb: 2\nc: 3\n";
let m: HashMap<String, i64> = from_str(yaml).unwrap();
assert_eq!(m.get("a"), Some(&1));
assert_eq!(m.get("b"), Some(&2));
assert_eq!(m.get("c"), Some(&3));
}
#[test]
fn final_streaming_drop_drains_map_after_late_error() {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct D {
a: i32,
b: i32,
c: i32,
}
let yaml = "a: 1\nb: 2\nc: not_an_int\nd: 4\ne: 5\n";
let r: Result<D, _> = from_str(yaml);
assert!(r.is_err());
}
#[test]
fn final_de_from_value_spanned_default_location() {
let v = Value::from(42_i64);
let s: Spanned<i64> = from_value(&v).expect("spanned from value (no ctx)");
assert_eq!(s.value, 42);
assert_eq!(s.start.line(), 0);
assert_eq!(s.start.column(), 0);
}
#[test]
fn final_streaming_drop_drains_seq_after_error() {
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct D {
nums: Vec<i32>,
}
let yaml = "nums:\n - 1\n - 2\n - bad\n - 4\n";
let r: Result<D, _> = from_str(yaml);
assert!(r.is_err());
}