#![allow(
clippy::approx_constant,
clippy::bool_assert_comparison,
dead_code,
unused_qualifications
)]
use noyalib::{ParserConfig, RcAnchor, Spanned, Value, from_str, from_str_with_config};
use serde::Deserialize;
#[test]
fn streaming_anchor_body_contains_alias() {
let yaml = "\
a: &a first
b: &b
inner: *a
copy: *b
";
let v: Value = from_str(yaml).unwrap();
let copy = v.get("copy").unwrap();
let inner = copy.get("inner").unwrap();
assert_eq!(inner.as_str(), Some("first"));
}
#[test]
fn streaming_merge_unknown_anchor_errors() {
let yaml = "base:\n k: 1\nderived:\n <<: *missing\n v: 2\n";
#[derive(Debug, serde::Deserialize)]
struct Doc {
base: std::collections::BTreeMap<String, i64>,
derived: std::collections::BTreeMap<String, i64>,
}
let err = from_str::<Doc>(yaml).unwrap_err();
assert!(err.to_string().to_lowercase().contains("anchor"));
}
#[test]
fn streaming_merge_with_nested_structures_in_local() {
let yaml = "\
base: &base
shared: 1
child:
<<: *base
items:
- 1
- 2
nested:
inner: yes
";
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Child {
shared: i64,
items: Vec<i64>,
nested: std::collections::BTreeMap<String, String>,
}
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Doc {
#[serde(skip)]
_base: Option<()>,
child: Child,
}
let v: Value = from_str(yaml).unwrap();
let child = v.get("child").unwrap();
assert_eq!(child.get("shared").and_then(|v| v.as_i64()), Some(1));
assert_eq!(
child
.get("items")
.and_then(|v| v.as_sequence())
.unwrap()
.len(),
2
);
}
#[test]
fn streaming_ignored_any_on_scalar() {
#[derive(Debug, serde::Deserialize)]
struct Doc {
keep: String,
#[serde(default, skip_deserializing)]
_ignored: (),
}
let yaml = "keep: yes\nskip_me: sentinel\n";
let d: Doc = from_str(yaml).unwrap();
assert_eq!(d.keep, "yes");
}
#[test]
fn streaming_ignored_any_on_alias() {
let yaml = "anchor: &a hello\n_skip: *a\nkeep: yes\n";
#[derive(Debug, serde::Deserialize)]
struct Doc {
anchor: String,
keep: String,
}
let d: Doc = from_str(yaml).unwrap();
assert_eq!(d.anchor, "hello");
assert_eq!(d.keep, "yes");
}
#[derive(Debug, serde::Serialize)]
struct NewtypeWrapper(String);
impl<'de> Deserialize<'de> for NewtypeWrapper {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct Inner {
tag: String,
value: String,
}
let inner = Inner::deserialize(deserializer)?;
Ok(NewtypeWrapper(format!("{}={}", inner.tag, inner.value)))
}
}
#[test]
fn streaming_newtype_struct_with_custom_tag() {
#[derive(Debug, serde::Deserialize)]
struct Doc {
field: std::collections::BTreeMap<String, String>,
}
let yaml = "field:\n a: b\n";
let _: Doc = from_str(yaml).unwrap();
}
#[test]
fn streaming_alias_bytes_guard() {
let cfg = ParserConfig::new().max_document_length(128);
let payload = "x".repeat(80);
let yaml = format!("a: &a {payload}\nlist:\n - *a\n - *a\n - *a\n",);
let err = from_str_with_config::<Value>(&yaml, &cfg).unwrap_err();
assert!(
err.to_string().to_lowercase().contains("limit")
|| err.to_string().to_lowercase().contains("document")
|| err.to_string().to_lowercase().contains("repetition")
);
}
#[test]
fn ast_wrap_err_emits_deserialize_with_location() {
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Doc {
#[serde(rename = "kind")]
_force: Spanned<String>,
#[allow(dead_code)]
value: Choice,
}
#[derive(Debug, serde::Deserialize)]
enum Choice {
A,
B,
}
let yaml = "kind: x\nvalue: C\n";
let err = from_str::<Doc>(yaml).unwrap_err();
assert!(err.to_string().contains('C') || err.to_string().contains("unknown"));
}
#[test]
fn ast_identifier_path_via_tag_key() {
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "type")]
#[allow(dead_code)]
enum Shape {
Square { side: i64 },
Circle { radius: i64 },
}
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Doc {
#[allow(dead_code)]
_force: Spanned<String>,
s: Shape,
}
let yaml = "_force: x\ns:\n type: Square\n side: 3\n";
let d: Doc = from_str(yaml).unwrap();
match d.s {
Shape::Square { side } => assert_eq!(side, 3),
_ => panic!("expected Square"),
}
}
#[test]
fn ast_deserialize_bytes_type_mismatch() {
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Doc {
#[allow(dead_code)]
_force: Spanned<String>,
#[serde(with = "serde_bytes")]
data: Vec<u8>,
}
let yaml = "_force: x\ndata: 42\n";
let err = from_str::<Doc>(yaml).unwrap_err();
assert!(err.to_string().to_lowercase().contains("bytes"));
}
#[test]
fn rc_anchor_three_aliases_reuses_same_id() {
let shared: RcAnchor<String> = RcAnchor::from("payload".to_string());
let doc = vec![shared.clone(), shared.clone(), shared];
let yaml = noyalib::to_string_tracking_shared(&doc).unwrap();
assert_eq!(yaml.matches("&id001").count(), 1);
assert_eq!(yaml.matches("*id001").count(), 2);
}
#[test]
fn value_float_nan_equals_self_structurally() {
let a = Value::from(f64::NAN);
let b = Value::from(f64::NAN);
assert_eq!(a, b);
}
#[test]
fn streaming_seq_drop_on_partial_read() {
let yaml = "- 1\n- 2\n- 3\n- 4\n";
let pair: (i32, i32) = from_str(yaml).unwrap();
assert_eq!(pair, (1, 2));
}
#[test]
fn streaming_map_drop_on_partial_read() {
let yaml = "a: 1\nb: 2\nc: 3\n";
#[derive(Debug, serde::Deserialize)]
struct Empty {}
let _: Empty = from_str(yaml).unwrap();
}