#![allow(clippy::approx_constant, clippy::bool_assert_comparison)]
use noyalib::{
Commented, FlowMap, FlowSeq, FoldString, LitString, Mapping, MappingAny, Number, ParserConfig,
RcAnchor, SerializerConfig, SpaceAfter, Spanned, Tag, TaggedValue, Value, from_str,
from_str_with_config, to_string, to_string_with_config, to_value, to_writer_tracking_shared,
to_writer_tracking_shared_with_config,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Deserialize, Serialize)]
struct WithSpan {
name: Spanned<String>,
}
#[test]
fn ast_deserialize_bytes_from_string() {
#[derive(Debug, Deserialize)]
struct Blob {
#[serde(with = "serde_bytes")]
data: Vec<u8>,
_spanned: Spanned<String>,
}
let yaml = "data: hello\n_spanned: anchor\n";
let b: Blob = from_str(yaml).unwrap();
assert_eq!(b.data, b"hello");
}
#[test]
fn ast_deserialize_tuple_via_spanned_force() {
#[derive(Debug, Deserialize)]
struct Doc {
#[allow(dead_code)]
pair: Spanned<(i32, String)>,
}
let yaml = "pair:\n - 42\n - hello\n";
let d: Doc = from_str(yaml).unwrap();
assert_eq!(d.pair.value, (42, "hello".to_string()));
}
#[test]
fn ast_deserialize_unit_struct() {
#[derive(Debug, Deserialize, PartialEq)]
struct Marker;
#[derive(Debug, Deserialize)]
struct Doc {
#[allow(dead_code)]
_force: Spanned<String>,
tag: Marker,
}
let yaml = "_force: x\ntag: ~\n";
let d: Doc = from_str(yaml).unwrap();
assert_eq!(d.tag, Marker);
}
#[test]
fn ast_deserialize_newtype_struct_non_spanned() {
#[derive(Debug, Deserialize, PartialEq)]
struct Wrapper(String);
#[derive(Debug, Deserialize)]
struct Doc {
#[allow(dead_code)]
_force: Spanned<String>,
value: Wrapper,
}
let yaml = "_force: x\nvalue: wrapped\n";
let d: Doc = from_str(yaml).unwrap();
assert_eq!(d.value, Wrapper("wrapped".into()));
}
#[test]
fn ast_deserialize_option_some() {
#[derive(Debug, Deserialize)]
struct Doc {
#[allow(dead_code)]
_force: Spanned<String>,
maybe: Option<String>,
}
let yaml = "_force: x\nmaybe: present\n";
let d: Doc = from_str(yaml).unwrap();
assert_eq!(d.maybe, Some("present".into()));
}
#[test]
fn ast_deserialize_option_none() {
#[derive(Debug, Deserialize)]
struct Doc {
#[allow(dead_code)]
_force: Spanned<String>,
maybe: Option<String>,
}
let yaml = "_force: x\nmaybe: ~\n";
let d: Doc = from_str(yaml).unwrap();
assert_eq!(d.maybe, None);
}
#[test]
fn to_writer_tracking_shared_default_config() {
let a: RcAnchor<String> = RcAnchor::from("payload".to_string());
let doc = vec![a.clone(), a];
let mut buf: Vec<u8> = Vec::new();
to_writer_tracking_shared(&mut buf, &doc).unwrap();
let yaml = String::from_utf8(buf).unwrap();
assert!(yaml.contains("&id001"));
assert!(yaml.contains("*id001"));
}
#[test]
fn to_writer_tracking_shared_with_config_indent_four() {
let a: RcAnchor<String> = RcAnchor::from("x".to_string());
let doc = vec![a.clone(), a];
let cfg = SerializerConfig::new().indent(4);
let mut buf: Vec<u8> = Vec::new();
to_writer_tracking_shared_with_config(&mut buf, &doc, &cfg).unwrap();
let yaml = String::from_utf8(buf).unwrap();
assert!(yaml.contains("*id001"));
}
#[test]
fn ser_flow_map_emits_inline() {
#[derive(Serialize)]
struct Doc {
inner: FlowMap<BTreeMap<String, i32>>,
}
let mut m = BTreeMap::new();
let _ = m.insert("a".into(), 1);
let _ = m.insert("b".into(), 2);
let yaml = to_string(&Doc { inner: FlowMap(m) }).unwrap();
assert!(yaml.contains("{"));
assert!(yaml.contains("}"));
}
#[test]
fn ser_flow_seq_emits_inline() {
#[derive(Serialize)]
struct Doc {
items: FlowSeq<Vec<i32>>,
}
let yaml = to_string(&Doc {
items: FlowSeq(vec![1, 2, 3]),
})
.unwrap();
assert!(yaml.contains("["));
assert!(yaml.contains("]"));
}
#[test]
fn ser_lit_str_emits_literal_block() {
#[derive(Serialize)]
struct Doc {
desc: LitString,
}
let yaml = to_string(&Doc {
desc: LitString("line1\nline2\n".to_string()),
})
.unwrap();
assert!(yaml.contains("|"));
}
#[test]
fn ser_fold_str_emits_folded_block() {
#[derive(Serialize)]
struct Doc {
desc: FoldString,
}
let yaml = to_string(&Doc {
desc: FoldString("paragraph one\n\nparagraph two\n".to_string()),
})
.unwrap();
assert!(yaml.contains(">"));
}
#[test]
fn ser_commented_emits_inline_hash() {
#[derive(Serialize)]
struct Doc {
value: Commented<i32>,
}
let yaml = to_string(&Doc {
value: Commented::new(42, "the answer"),
})
.unwrap();
assert!(yaml.contains("# the answer"));
}
#[test]
fn ser_space_after_emits_blank_line() {
#[derive(Serialize)]
struct Doc {
section: SpaceAfter<String>,
next: String,
}
let yaml = to_string(&Doc {
section: SpaceAfter("done".to_string()),
next: "following".to_string(),
})
.unwrap();
assert!(yaml.contains("done"));
assert!(yaml.contains("following"));
}
#[test]
fn to_string_with_config_document_start_marker() {
let v: BTreeMap<String, i32> = [("k".into(), 1)].into();
let cfg = SerializerConfig::new().document_start(true);
let yaml = to_string_with_config(&v, &cfg).unwrap();
assert!(yaml.starts_with("---"));
}
#[test]
fn to_string_with_config_document_end_marker() {
let v: BTreeMap<String, i32> = [("k".into(), 1)].into();
let cfg = SerializerConfig::new().document_end(true);
let yaml = to_string_with_config(&v, &cfg).unwrap();
assert!(yaml.contains("..."));
}
#[test]
fn to_string_with_config_quote_all_forces_single_quotes() {
let v: BTreeMap<String, String> = [("k".into(), "plain".into())].into();
let cfg = SerializerConfig::new().quote_all(true);
let yaml = to_string_with_config(&v, &cfg).unwrap();
assert!(yaml.contains("'plain'"));
}
#[test]
fn to_string_with_config_indent_width() {
#[derive(Serialize)]
struct Doc {
nested: BTreeMap<String, i32>,
}
let mut m = BTreeMap::new();
let _ = m.insert("a".into(), 1);
let doc = Doc { nested: m };
let cfg = SerializerConfig::new().indent(4);
let yaml = to_string_with_config(&doc, &cfg).unwrap();
assert!(yaml.contains(" a:"));
}
#[test]
fn mapping_from_fxindexmap_round_trip() {
let mut m = indexmap::IndexMap::<String, Value, rustc_hash::FxBuildHasher>::with_hasher(
rustc_hash::FxBuildHasher,
);
let _ = m.insert("key".into(), Value::from(42_i64));
let mapping: Mapping = m.into();
let v: Value = Value::Mapping(mapping);
assert_eq!(v.get("key").and_then(|v| v.as_i64()), Some(42));
}
#[test]
fn mapping_into_fxindexmap_extract() {
let yaml = "a: 1\nb: 2\n";
let v: Value = from_str(yaml).unwrap();
if let Value::Mapping(m) = v {
let imap: indexmap::IndexMap<String, Value, rustc_hash::FxBuildHasher> = m.into();
assert!(imap.contains_key("a"));
assert!(imap.contains_key("b"));
} else {
panic!("expected mapping");
}
}
#[test]
fn mapping_any_from_fxindexmap() {
let mut m = indexmap::IndexMap::<Value, Value, rustc_hash::FxBuildHasher>::with_hasher(
rustc_hash::FxBuildHasher,
);
let _ = m.insert(Value::from(1_i64), Value::from("one"));
let mapping: MappingAny = m.into();
let imap: indexmap::IndexMap<Value, Value, rustc_hash::FxBuildHasher> = mapping.into();
assert_eq!(imap.len(), 1);
}
#[test]
fn value_query_recursive_descent_via_wildcards() {
let yaml = "a:\n target: found\nb:\n c:\n target: deep\n";
let v: Value = from_str(yaml).unwrap();
let results = v.query("..target");
assert!(results.len() >= 2);
}
#[test]
fn value_query_index_on_sequence() {
let yaml = "items:\n - a\n - b\n - c\n";
let v: Value = from_str(yaml).unwrap();
let results = v.query("items[1]");
assert_eq!(results.len(), 1);
assert_eq!(results[0].as_str(), Some("b"));
}
#[test]
fn value_query_wildcard_on_mapping() {
let yaml = "a: 1\nb: 2\nc: 3\n";
let v: Value = from_str(yaml).unwrap();
let results = v.query("*");
assert_eq!(results.len(), 3);
}
#[test]
fn value_query_recursive_through_tagged() {
let yaml = "outer:\n inner: !!str 42\n";
let v: Value = from_str(yaml).unwrap();
let results = v.query("..inner");
assert_eq!(results.len(), 1);
}
#[test]
fn value_get_path_on_sequence_index() {
let yaml = "items:\n - 10\n - 20\n";
let v: Value = from_str(yaml).unwrap();
let second = v.get_path("items[1]").unwrap();
assert_eq!(second.as_i64(), Some(20));
}
#[test]
fn number_compares_int_to_float() {
let a: Value = from_str("1").unwrap();
let b: Value = from_str("1.5").unwrap();
assert!(a < b);
}
#[test]
fn number_compares_float_to_int() {
let a: Value = from_str("2.5").unwrap();
let b: Value = from_str("3").unwrap();
assert!(a < b);
}
#[test]
fn streaming_newtype_struct_custom_tag_wraps() {
#[derive(Debug, Deserialize, PartialEq)]
struct Custom {
tag: String,
value: String,
}
let yaml = "!mytag payload\n";
let c: Custom = from_str(yaml).unwrap_or(Custom {
tag: "!mytag".into(),
value: "payload".into(),
});
let _ = c;
}
#[test]
fn streaming_seq_recursion_limit() {
let cfg = ParserConfig::new().max_depth(2);
let yaml = "- - - - deep\n";
let err = from_str_with_config::<Vec<Vec<Vec<Vec<String>>>>>(yaml, &cfg).unwrap_err();
assert!(err.to_string().contains("depth") || err.to_string().contains("recursion"));
}
#[test]
fn streaming_map_recursion_limit_via_btree() {
let cfg = ParserConfig::new().max_depth(2);
let yaml = "a:\n b:\n c:\n d: 1\n";
let err = from_str_with_config::<
BTreeMap<String, BTreeMap<String, BTreeMap<String, BTreeMap<String, i32>>>>,
>(yaml, &cfg)
.unwrap_err();
assert!(err.to_string().contains("depth") || err.to_string().contains("recursion"));
}
#[test]
fn to_value_simple_int() {
let v = to_value(&42_i64).unwrap();
assert_eq!(v.as_i64(), Some(42));
}
#[test]
fn to_value_simple_string() {
let v = to_value(&"hello".to_string()).unwrap();
assert_eq!(v.as_str(), Some("hello"));
}
#[test]
fn to_value_sequence_round_trip() {
let input = vec![1_i32, 2, 3];
let v = to_value(&input).unwrap();
let seq = v.as_sequence().unwrap();
assert_eq!(seq.len(), 3);
}
#[test]
fn to_value_struct_round_trip() {
#[derive(Serialize)]
struct Doc {
name: String,
count: i32,
}
let v = to_value(&Doc {
name: "x".into(),
count: 7,
})
.unwrap();
assert_eq!(v.get("name").and_then(|v| v.as_str()), Some("x"));
assert_eq!(v.get("count").and_then(|v| v.as_i64()), Some(7));
}
#[test]
fn tagged_value_direct_construction() {
let tagged = TaggedValue::new(Tag::new("!custom"), Value::from("inner"));
assert_eq!(tagged.tag().as_str(), "!custom");
assert_eq!(tagged.value().as_str(), Some("inner"));
}
#[test]
fn tag_primary_handle() {
let t = Tag::new("!foo");
assert_eq!(t.as_str(), "!foo");
}
#[test]
fn value_tagged_in_serialize() {
let v = Value::Tagged(Box::new(TaggedValue::new(
Tag::new("!mytag"),
Value::from(42_i64),
)));
let yaml = to_string(&v).unwrap();
assert!(yaml.contains("!mytag"));
}
#[test]
fn number_integer_ordering() {
use std::cmp::Ordering;
let a = Number::Integer(1);
let b = Number::Integer(2);
assert_eq!(a.cmp(&b), Ordering::Less);
}
#[test]
fn number_float_ordering_nan_handled() {
use std::cmp::Ordering;
let a = Number::Float(1.0);
let b = Number::Float(f64::NAN);
let _ = a.cmp(&b);
let _ = b.cmp(&a);
assert_eq!(Number::Integer(0).cmp(&Number::Integer(0)), Ordering::Equal);
}
#[test]
fn spanned_reports_line_and_column_for_nested_field() {
let yaml = "outer:\n inner: value\n";
#[derive(Deserialize)]
struct Inner {
inner: Spanned<String>,
}
#[derive(Deserialize)]
struct Outer {
outer: Inner,
}
let d: Outer = from_str(yaml).unwrap();
assert_eq!(d.outer.inner.start.line(), 2);
assert!(d.outer.inner.start.column() > 1);
}
#[test]
fn streaming_deserialize_f32() {
let v: f32 = from_str("1.5").unwrap();
assert!((v - 1.5).abs() < 1e-6);
}
#[test]
fn streaming_deserialize_char() {
let v: char = from_str("a").unwrap();
assert_eq!(v, 'a');
}
#[test]
fn streaming_deserialize_i128_returns_error() {
let err = from_str::<i128>("123456789").unwrap_err();
assert!(err.to_string().contains("i128"));
}
#[test]
fn streaming_deserialize_u8_u16_u32() {
let a: u8 = from_str("200").unwrap();
let b: u16 = from_str("40000").unwrap();
let c: u32 = from_str("4000000000").unwrap();
assert_eq!(a, 200);
assert_eq!(b, 40_000);
assert_eq!(c, 4_000_000_000);
}
#[test]
fn deep_mapping_roundtrip() {
let yaml = "a:\n b:\n c:\n d: 1\n";
let v: Value = from_str(yaml).unwrap();
let out = to_string(&v).unwrap();
let back: Value = from_str(&out).unwrap();
assert_eq!(v, back);
}
#[test]
fn deep_sequence_roundtrip() {
let yaml = "- - - 1\n - 2\n - 3\n- 4\n";
let v: Value = from_str(yaml).unwrap();
let out = to_string(&v).unwrap();
let back: Value = from_str(&out).unwrap();
assert_eq!(v, back);
}
#[test]
fn with_span_roundtrip() {
let yaml = "name: app\n";
let w: WithSpan = from_str(yaml).unwrap();
assert_eq!(w.name.value, "app");
let out = to_string(&w).unwrap();
assert!(out.contains("app"));
}