use noyalib::ParserConfig;
use noyalib::borrowed::{BorrowedValue, from_str_borrowed};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn hash(v: &BorrowedValue<'_>) -> u64 {
let mut h = DefaultHasher::new();
v.hash(&mut h);
h.finish()
}
#[test]
fn hash_null_is_stable() {
let a: BorrowedValue<'_> = from_str_borrowed("~").unwrap();
let b: BorrowedValue<'_> = from_str_borrowed("null").unwrap();
assert_eq!(hash(&a), hash(&b));
}
#[test]
fn hash_bool() {
let t: BorrowedValue<'_> = from_str_borrowed("true").unwrap();
let f: BorrowedValue<'_> = from_str_borrowed("false").unwrap();
assert_ne!(hash(&t), hash(&f));
}
#[test]
fn hash_int_vs_float() {
let i: BorrowedValue<'_> = from_str_borrowed("42").unwrap();
let f: BorrowedValue<'_> = from_str_borrowed("1.5").unwrap();
assert_ne!(hash(&i), hash(&f));
}
#[test]
fn hash_string() {
let a: BorrowedValue<'_> = from_str_borrowed("hello").unwrap();
let b: BorrowedValue<'_> = from_str_borrowed("world").unwrap();
assert_ne!(hash(&a), hash(&b));
}
#[test]
fn hash_sequence() {
let s: BorrowedValue<'_> = from_str_borrowed("- 1\n- 2\n").unwrap();
let t: BorrowedValue<'_> = from_str_borrowed("- 1\n- 2\n").unwrap();
assert_eq!(hash(&s), hash(&t));
}
#[test]
fn hash_mapping() {
let m: BorrowedValue<'_> = from_str_borrowed("a: 1\nb: 2\n").unwrap();
let n: BorrowedValue<'_> = from_str_borrowed("a: 1\nb: 2\n").unwrap();
assert_eq!(hash(&m), hash(&n));
}
#[test]
fn hash_differs_across_variants() {
let null: BorrowedValue<'_> = from_str_borrowed("null").unwrap();
let int: BorrowedValue<'_> = from_str_borrowed("0").unwrap();
let bool_v: BorrowedValue<'_> = from_str_borrowed("false").unwrap();
assert_ne!(hash(&null), hash(&int));
assert_ne!(hash(&int), hash(&bool_v));
assert_ne!(hash(&null), hash(&bool_v));
}
#[test]
fn serialize_null_json() {
let v: BorrowedValue<'_> = from_str_borrowed("~").unwrap();
assert_eq!(serde_json::to_string(&v).unwrap(), "null");
}
#[test]
fn serialize_bool_json() {
let v: BorrowedValue<'_> = from_str_borrowed("true").unwrap();
assert_eq!(serde_json::to_string(&v).unwrap(), "true");
}
#[test]
fn serialize_int_json() {
let v: BorrowedValue<'_> = from_str_borrowed("42").unwrap();
assert_eq!(serde_json::to_string(&v).unwrap(), "42");
}
#[test]
fn serialize_float_json() {
let v: BorrowedValue<'_> = from_str_borrowed("1.5").unwrap();
assert_eq!(serde_json::to_string(&v).unwrap(), "1.5");
}
#[test]
fn serialize_string_json() {
let v: BorrowedValue<'_> = from_str_borrowed("hello").unwrap();
assert_eq!(serde_json::to_string(&v).unwrap(), "\"hello\"");
}
#[test]
fn serialize_sequence_json() {
let v: BorrowedValue<'_> = from_str_borrowed("[1, 2, 3]").unwrap();
assert_eq!(serde_json::to_string(&v).unwrap(), "[1,2,3]");
}
#[test]
fn serialize_mapping_json() {
let v: BorrowedValue<'_> = from_str_borrowed("a: 1\n").unwrap();
let out = serde_json::to_string(&v).unwrap();
assert_eq!(out, "{\"a\":1}");
}
#[test]
fn ord_variants_rank_consistently() {
let null: BorrowedValue<'_> = from_str_borrowed("~").unwrap();
let bool_v: BorrowedValue<'_> = from_str_borrowed("false").unwrap();
let int: BorrowedValue<'_> = from_str_borrowed("0").unwrap();
let str_v: BorrowedValue<'_> = from_str_borrowed("hello").unwrap();
let seq: BorrowedValue<'_> = from_str_borrowed("[1]").unwrap();
let map: BorrowedValue<'_> = from_str_borrowed("a: 1\n").unwrap();
assert!(null < bool_v);
assert!(bool_v < int);
assert!(int < str_v);
assert!(str_v < seq);
assert!(seq < map);
}
#[test]
fn ord_same_variant_compares_content() {
let a: BorrowedValue<'_> = from_str_borrowed("1").unwrap();
let b: BorrowedValue<'_> = from_str_borrowed("2").unwrap();
assert!(a < b);
let x: BorrowedValue<'_> = from_str_borrowed("abc").unwrap();
let y: BorrowedValue<'_> = from_str_borrowed("xyz").unwrap();
assert!(x < y);
let f: BorrowedValue<'_> = from_str_borrowed("false").unwrap();
let t: BorrowedValue<'_> = from_str_borrowed("true").unwrap();
assert!(f < t);
}
#[test]
fn ord_equal_nulls() {
let a: BorrowedValue<'_> = from_str_borrowed("~").unwrap();
let b: BorrowedValue<'_> = from_str_borrowed("null").unwrap();
assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
}
#[test]
fn ord_sequence_by_content() {
let a: BorrowedValue<'_> = from_str_borrowed("[1, 2]").unwrap();
let b: BorrowedValue<'_> = from_str_borrowed("[1, 3]").unwrap();
assert!(a < b);
}
#[test]
fn ord_mapping_by_length() {
let a: BorrowedValue<'_> = from_str_borrowed("a: 1\n").unwrap();
let b: BorrowedValue<'_> = from_str_borrowed("a: 1\nb: 2\n").unwrap();
assert!(a < b);
}
#[test]
fn partial_cmp_delegates_to_cmp() {
let a: BorrowedValue<'_> = from_str_borrowed("1").unwrap();
let b: BorrowedValue<'_> = from_str_borrowed("2").unwrap();
assert_eq!(a.partial_cmp(&b), Some(core::cmp::Ordering::Less));
}
#[test]
fn get_path_index_on_sequence() {
let v: BorrowedValue<'_> = from_str_borrowed("items:\n - a\n - b\n - c\n").unwrap();
let third = v.get_path("items[2]").expect("index hit");
assert_eq!(third.as_str(), Some("c"));
}
#[test]
fn get_path_index_on_non_sequence_returns_none() {
let v: BorrowedValue<'_> = from_str_borrowed("scalar").unwrap();
assert!(v.get_path("[0]").is_none());
}
#[test]
fn get_path_key_on_non_mapping_returns_none() {
let v: BorrowedValue<'_> = from_str_borrowed("[1, 2]").unwrap();
assert!(v.get_path("foo").is_none());
}
#[test]
fn get_path_through_wildcard_returns_first_match() {
let v: BorrowedValue<'_> = from_str_borrowed("items:\n - a\n - b\n").unwrap();
let first = v.get_path("items[*]").expect("wildcard hit");
assert_eq!(first.as_str(), Some("a"));
}
#[test]
fn into_owned_null() {
let v: BorrowedValue<'_> = from_str_borrowed("~").unwrap();
let owned = v.into_owned();
assert!(owned.is_null());
}
#[test]
fn into_owned_bool() {
let v: BorrowedValue<'_> = from_str_borrowed("false").unwrap();
let owned = v.into_owned();
assert_eq!(owned.as_bool(), Some(false));
}
#[test]
fn into_owned_integer() {
let v: BorrowedValue<'_> = from_str_borrowed("42").unwrap();
let owned = v.into_owned();
assert_eq!(owned.as_i64(), Some(42));
}
#[test]
fn into_owned_float() {
let v: BorrowedValue<'_> = from_str_borrowed("2.5").unwrap();
let owned = v.into_owned();
assert!((owned.as_f64().unwrap() - 2.5).abs() < 1e-9);
}
#[test]
fn into_owned_string() {
let v: BorrowedValue<'_> = from_str_borrowed("hello").unwrap();
let owned = v.into_owned();
assert_eq!(owned.as_str(), Some("hello"));
}
#[test]
fn into_owned_sequence_with_mixed_types() {
let v: BorrowedValue<'_> = from_str_borrowed("- 1\n- a\n- true\n").unwrap();
let owned = v.into_owned();
let seq = owned.as_sequence().expect("seq");
assert_eq!(seq[0].as_i64(), Some(1));
assert_eq!(seq[1].as_str(), Some("a"));
assert_eq!(seq[2].as_bool(), Some(true));
}
#[test]
fn query_wildcard_on_mapping_iterates_values() {
let v: BorrowedValue<'_> = from_str_borrowed("{a: 1, b: 2, c: 3}").unwrap();
let results = v.query("*");
assert_eq!(results.len(), 3);
}
#[test]
fn query_recursive_descent_with_key_target() {
let yaml = "a:\n b:\n target: found1\n c:\n target: found2\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let results = v.query("..target");
assert!(
results.len() >= 2,
"expected at least 2 hits, got {}",
results.len()
);
}
#[test]
fn query_chained_segments_through_mapping_and_sequence() {
let yaml =
"users:\n - name: alice\n roles: [admin, user]\n - name: bob\n roles: [user]\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let results = v.query("users[*].roles[0]");
assert_eq!(results.len(), 2);
assert_eq!(results[0].as_str(), Some("admin"));
assert_eq!(results[1].as_str(), Some("user"));
}
#[test]
fn from_str_borrowed_uses_default_config() {
let yaml = "a: 1\n".repeat(100);
let _ = from_str_borrowed(&yaml).unwrap();
}
#[test]
fn borrowed_parses_positive_infinity() {
let v: BorrowedValue<'_> = from_str_borrowed(".inf").unwrap();
assert_eq!(v.as_str(), None);
}
#[test]
fn borrowed_parses_negative_infinity() {
let v: BorrowedValue<'_> = from_str_borrowed("-.inf").unwrap();
assert_eq!(v.as_str(), None);
}
#[test]
fn borrowed_parses_nan() {
let v: BorrowedValue<'_> = from_str_borrowed(".nan").unwrap();
assert_eq!(v.as_str(), None);
}
#[test]
fn borrowed_resolves_simple_alias() {
let yaml = "anchor: &a hello\nalias: *a\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let map = v.as_mapping().expect("top-level must be a mapping");
assert_eq!(map.get("anchor").and_then(|v| v.as_str()), Some("hello"));
assert_eq!(map.get("alias").and_then(|v| v.as_str()), Some("hello"));
}
#[test]
fn borrowed_into_owned_matches_from_str() {
let yaml = "name: app\nversion: 1.2.3\nfeatures:\n - auth\n - api\n";
let borrowed: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let via_borrowed = borrowed.into_owned();
let via_owned: noyalib::Value = noyalib::from_str(yaml).unwrap();
assert_eq!(via_borrowed, via_owned);
}
#[test]
fn deeply_nested_borrowed_errors() {
let mut yaml = String::new();
for _ in 0..500 {
yaml.push_str("a:\n ");
}
yaml.push_str("stop");
let result = from_str_borrowed(&yaml);
let _ = result;
}
#[test]
fn document_length_limit_checked_by_owned_side() {
let config = ParserConfig::new().max_document_length(10);
let yaml = "this string is way too long for the document limit";
assert!(noyalib::from_str_with_config::<noyalib::Value>(yaml, &config).is_err());
}
#[test]
fn query_recursive_sequence_traversal() {
let yaml = "users:\n - name: alice\n - name: bob\nadmins:\n - name: root\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let results = v.query("..name");
assert!(results.len() >= 3);
}
#[test]
fn query_recursive_through_nested_sequences() {
let yaml = "a:\n - b:\n - c: found\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let results = v.query("..c");
assert!(!results.is_empty());
assert_eq!(results[0].as_str(), Some("found"));
}
#[test]
fn query_recursive_on_scalar_returns_nothing() {
let v: BorrowedValue<'_> = from_str_borrowed("42").unwrap();
let results = v.query("..any_key");
assert!(results.is_empty());
}
#[test]
fn query_wildcard_on_scalar_returns_empty() {
let v: BorrowedValue<'_> = from_str_borrowed("42").unwrap();
let results = v.query("*");
assert!(results.is_empty());
}
#[test]
fn borrowed_tagged_scalar_is_accepted() {
let v: BorrowedValue<'_> = from_str_borrowed("!!str 42").unwrap();
let _ = v;
}
#[test]
fn borrowed_custom_tag_is_accepted() {
let v: BorrowedValue<'_> = from_str_borrowed("!mytag hello").unwrap();
let _ = v;
}
#[test]
fn deep_borrowed_sequence_may_error() {
let mut yaml = String::new();
for _ in 0..200 {
yaml.push_str("- ");
}
yaml.push_str("deep");
let _ = from_str_borrowed(&yaml);
}
#[test]
fn deep_borrowed_mapping_may_error() {
let mut yaml = String::new();
for _ in 0..200 {
yaml.push_str("a:\n ");
}
yaml.push_str("done");
let _ = from_str_borrowed(&yaml);
}
#[test]
fn borrowed_double_quoted_scalar() {
let v: BorrowedValue<'_> = from_str_borrowed("\"hello world\"").unwrap();
assert_eq!(v.as_str(), Some("hello world"));
}
#[test]
fn borrowed_single_quoted_numeric_stays_string() {
let v: BorrowedValue<'_> = from_str_borrowed("'42'").unwrap();
assert_eq!(v.as_str(), Some("42"));
assert_eq!(v.as_i64(), None);
}
#[test]
fn borrowed_literal_block_scalar() {
let yaml = "key: |\n line1\n line2\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let k = v.as_mapping().unwrap().get("key").unwrap();
assert!(k.as_str().is_some());
}
#[test]
fn borrowed_folded_block_scalar() {
let yaml = "key: >\n folded text\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let k = v.as_mapping().unwrap().get("key").unwrap();
assert!(k.as_str().is_some());
}
#[test]
fn query_recursive_descent_on_empty_mapping() {
let v: BorrowedValue<'_> = from_str_borrowed("{}").unwrap();
let results = v.query("..anything");
assert!(results.is_empty());
}
#[test]
fn query_recursive_descent_on_empty_sequence() {
let v: BorrowedValue<'_> = from_str_borrowed("[]").unwrap();
let results = v.query("..anything");
assert!(results.is_empty());
}
#[test]
fn borrowed_sequence_past_depth_limit_errors() {
let mut yaml = String::new();
for _ in 0..130 {
yaml.push_str("- ");
}
yaml.push_str("deep");
let result = from_str_borrowed(&yaml);
assert!(result.is_err());
}
#[test]
fn borrowed_mapping_past_depth_limit_errors() {
let mut yaml = String::new();
for _ in 0..130 {
yaml.push_str("a:\n ");
}
yaml.push_str("stop");
let result = from_str_borrowed(&yaml);
assert!(result.is_err());
}
#[test]
fn query_recursive_descent_into_sequence_inner() {
let yaml = "top:\n - key: found\n - other: x\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let results = v.query("..key");
assert_eq!(results.len(), 1);
assert_eq!(results[0].as_str(), Some("found"));
}
#[test]
fn query_recursive_descent_with_index_target() {
let yaml = "a:\n items:\n - first\n - second\nb:\n items:\n - x\n - y\n";
let v: BorrowedValue<'_> = from_str_borrowed(yaml).unwrap();
let results = v.query("..items[0]");
assert!(results.len() >= 2);
}
#[test]
fn query_recursive_descent_at_end_is_noop() {
let v: BorrowedValue<'_> = from_str_borrowed("a: 1\n").unwrap();
let results = v.query("..");
let _ = results;
}
#[test]
fn numeric_prefix_but_not_a_number_becomes_string() {
let v: BorrowedValue<'_> = from_str_borrowed("-abc").unwrap();
assert_eq!(v.as_str(), Some("-abc"));
}
#[test]
fn dot_prefix_non_numeric_stays_string() {
let v: BorrowedValue<'_> = from_str_borrowed(".dotfile").unwrap();
assert_eq!(v.as_str(), Some(".dotfile"));
}
#[test]
fn borrowed_document_length_limit_enforced() {
use noyalib::borrowed::from_str_borrowed_with_config;
let config = ParserConfig::new().max_document_length(10);
let yaml = "this yaml is way longer than ten bytes";
let err = from_str_borrowed_with_config(yaml, &config).unwrap_err();
assert!(err.to_string().contains("maximum length"));
}
#[test]
fn borrowed_tight_depth_limit_errors_on_nesting() {
use noyalib::borrowed::from_str_borrowed_with_config;
let config = ParserConfig::new().max_depth(2);
let yaml = "- - - - deeper\n";
let result = from_str_borrowed_with_config(yaml, &config);
assert!(result.is_err());
}
#[test]
fn borrowed_with_config_same_as_default() {
use noyalib::borrowed::from_str_borrowed_with_config;
let yaml = "k: v\n";
let default = ParserConfig::new();
let v = from_str_borrowed_with_config(yaml, &default).unwrap();
let v2 = from_str_borrowed(yaml).unwrap();
assert_eq!(v.into_owned(), v2.into_owned());
}