use crate::node_ref::NodeRef;
use crate::scalar_parse;
use std::fmt;
#[derive(Clone, Copy)]
pub struct ValueRef<'doc> {
node: NodeRef<'doc>,
}
impl<'doc> ValueRef<'doc> {
#[inline]
pub fn new(node: NodeRef<'doc>) -> Self {
ValueRef { node }
}
#[inline]
pub fn as_node(&self) -> NodeRef<'doc> {
self.node
}
#[inline]
pub fn is_scalar(&self) -> bool {
self.node.is_scalar()
}
#[inline]
pub fn is_sequence(&self) -> bool {
self.node.is_sequence()
}
#[inline]
pub fn is_mapping(&self) -> bool {
self.node.is_mapping()
}
pub fn is_null(&self) -> bool {
if !self.node.is_scalar() {
return false;
}
if self.node.is_non_plain() {
return false;
}
match self.node.scalar_str() {
Ok(s) => scalar_parse::is_null(s),
Err(_) => false,
}
}
pub fn as_str(&self) -> Option<&'doc str> {
self.node.scalar_str().ok()
}
pub fn as_bytes(&self) -> Option<&'doc [u8]> {
self.node.scalar_bytes().ok()
}
pub fn as_bool(&self) -> Option<bool> {
if !self.node.is_scalar() {
return None;
}
if self.node.is_non_plain() {
return None;
}
let s = self.node.scalar_str().ok()?;
scalar_parse::parse_bool(s)
}
pub fn as_i64(&self) -> Option<i64> {
if !self.node.is_scalar() {
return None;
}
if self.node.is_non_plain() {
return None;
}
let s = self.node.scalar_str().ok()?;
scalar_parse::parse_i64(s)
}
pub fn as_u64(&self) -> Option<u64> {
if !self.node.is_scalar() {
return None;
}
if self.node.is_non_plain() {
return None;
}
let s = self.node.scalar_str().ok()?;
scalar_parse::parse_u64(s)
}
pub fn as_f64(&self) -> Option<f64> {
if !self.node.is_scalar() {
return None;
}
if self.node.is_non_plain() {
return None;
}
let s = self.node.scalar_str().ok()?;
scalar_parse::parse_f64(s)
}
pub fn at_path(&self, path: &str) -> Option<ValueRef<'doc>> {
self.node.at_path(path).map(ValueRef::new)
}
pub fn get(&self, key: &str) -> Option<ValueRef<'doc>> {
self.node.map_get(key).map(ValueRef::new)
}
pub fn index(&self, i: i32) -> Option<ValueRef<'doc>> {
self.node.seq_get(i).map(ValueRef::new)
}
pub fn seq_len(&self) -> Option<usize> {
self.node.seq_len().ok()
}
pub fn map_len(&self) -> Option<usize> {
self.node.map_len().ok()
}
pub fn seq_iter(&self) -> impl Iterator<Item = ValueRef<'doc>> {
self.node.seq_iter().map(ValueRef::new)
}
pub fn map_iter(&self) -> impl Iterator<Item = (ValueRef<'doc>, ValueRef<'doc>)> {
self.node
.map_iter()
.map(|(k, v)| (ValueRef::new(k), ValueRef::new(v)))
}
pub fn tag(&self) -> Option<&'doc str> {
self.node.tag_str().ok().flatten()
}
}
impl fmt::Debug for ValueRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_null() {
write!(f, "ValueRef(null)")
} else if let Some(b) = self.as_bool() {
write!(f, "ValueRef({})", b)
} else if let Some(n) = self.as_i64() {
write!(f, "ValueRef({})", n)
} else if let Some(n) = self.as_f64() {
write!(f, "ValueRef({})", n)
} else if let Some(s) = self.as_str() {
write!(f, "ValueRef({:?})", s)
} else if self.is_sequence() {
write!(f, "ValueRef(sequence[{}])", self.seq_len().unwrap_or(0))
} else if self.is_mapping() {
write!(f, "ValueRef(mapping[{}])", self.map_len().unwrap_or(0))
} else {
write!(f, "ValueRef(?)")
}
}
}
impl fmt::Display for ValueRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.node)
}
}
#[cfg(test)]
mod tests {
use crate::Document;
#[test]
fn test_as_str() {
let doc = Document::parse_str("key: hello").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("key").unwrap().as_str(), Some("hello"));
}
#[test]
fn test_as_bytes() {
let doc = Document::parse_str("key: hello").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(
root.get("key").unwrap().as_bytes(),
Some(b"hello".as_slice())
);
}
#[test]
fn test_as_bool() {
let doc = Document::parse_str("yes_val: yes\nno_val: no\ntrue_val: true").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("yes_val").unwrap().as_bool(), Some(true));
assert_eq!(root.get("no_val").unwrap().as_bool(), Some(false));
assert_eq!(root.get("true_val").unwrap().as_bool(), Some(true));
}
#[test]
fn test_quoted_not_interpreted() {
let doc = Document::parse_str("quoted: 'true'\nunquoted: true").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("quoted").unwrap().as_bool(), None);
assert_eq!(root.get("quoted").unwrap().as_str(), Some("true"));
assert_eq!(root.get("unquoted").unwrap().as_bool(), Some(true));
}
#[test]
fn test_as_i64() {
let doc = Document::parse_str("num: 42\nneg: -10\nhex: 0xFF").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("num").unwrap().as_i64(), Some(42));
assert_eq!(root.get("neg").unwrap().as_i64(), Some(-10));
assert_eq!(root.get("hex").unwrap().as_i64(), Some(255));
}
#[test]
fn test_integer_boundary_values() {
let doc = Document::parse_str(&format!(
"max_i64: {}\nlarge_neg: {}\nmax_u64: {}",
i64::MAX,
i64::MIN + 1,
u64::MAX
))
.unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("max_i64").unwrap().as_i64(), Some(i64::MAX));
assert_eq!(root.get("large_neg").unwrap().as_i64(), Some(i64::MIN + 1));
assert_eq!(root.get("max_u64").unwrap().as_u64(), Some(u64::MAX));
}
#[test]
fn test_integer_overflow_returns_none() {
let doc = Document::parse_str(&format!(
"too_big: {}0\ntoo_small: -{}0",
i64::MAX,
i64::MAX
))
.unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("too_big").unwrap().as_i64(), None);
assert_eq!(root.get("too_small").unwrap().as_i64(), None);
}
#[test]
fn test_as_u64_rejects_negative() {
let doc = Document::parse_str("neg: -10\npos: 42").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("neg").unwrap().as_u64(), None);
assert_eq!(root.get("pos").unwrap().as_u64(), Some(42));
}
#[test]
fn test_as_f64() {
let doc = Document::parse_str("val: 2.5\ninf: .inf\nnan: .nan").unwrap();
let root = doc.root_value().unwrap();
assert!((root.get("val").unwrap().as_f64().unwrap() - 2.5).abs() < 0.01);
assert!(root.get("inf").unwrap().as_f64().unwrap().is_infinite());
assert!(root.get("nan").unwrap().as_f64().unwrap().is_nan());
}
#[test]
fn test_as_f64_special_values() {
let doc = Document::parse_str("pinf: +.inf\nninf: -.inf\nnan: .NaN").unwrap();
let root = doc.root_value().unwrap();
let pinf = root.get("pinf").unwrap().as_f64().unwrap();
let ninf = root.get("ninf").unwrap().as_f64().unwrap();
let nan = root.get("nan").unwrap().as_f64().unwrap();
assert!(pinf.is_infinite() && pinf.is_sign_positive());
assert!(ninf.is_infinite() && ninf.is_sign_negative());
assert!(nan.is_nan());
}
#[test]
fn test_as_f64_exponent() {
let doc = Document::parse_str("exp: 1e3\nneg_exp: 1.5e-2").unwrap();
let root = doc.root_value().unwrap();
assert!((root.get("exp").unwrap().as_f64().unwrap() - 1000.0).abs() < 0.01);
assert!((root.get("neg_exp").unwrap().as_f64().unwrap() - 0.015).abs() < 0.0001);
}
#[test]
fn test_as_f64_from_integer() {
let doc = Document::parse_str("int: 42").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("int").unwrap().as_f64(), Some(42.0));
}
#[test]
fn test_is_null() {
let doc = Document::parse_str("null_val: null\ntilde: ~\nempty:\nstr: 'null'").unwrap();
let root = doc.root_value().unwrap();
assert!(root.get("null_val").unwrap().is_null());
assert!(root.get("tilde").unwrap().is_null());
assert!(root.get("empty").unwrap().is_null());
assert!(!root.get("str").unwrap().is_null());
}
#[test]
fn test_is_null_case_insensitive() {
let doc = Document::parse_str("n1: NULL\nn2: Null\nn3: NuLl").unwrap();
let root = doc.root_value().unwrap();
assert!(root.get("n1").unwrap().is_null());
assert!(root.get("n2").unwrap().is_null());
assert!(root.get("n3").unwrap().is_null());
}
#[test]
fn test_literal_block_not_interpreted() {
let doc = Document::parse_str("literal: |\n true").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("literal").unwrap().as_bool(), None);
assert!(root.get("literal").unwrap().as_str().is_some());
}
#[test]
fn test_folded_block_not_interpreted() {
let doc = Document::parse_str("folded: >\n 42").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("folded").unwrap().as_i64(), None);
assert!(root.get("folded").unwrap().as_str().is_some());
}
#[test]
fn test_double_quoted_not_interpreted() {
let doc = Document::parse_str("quoted: \"42\"").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("quoted").unwrap().as_i64(), None);
assert_eq!(root.get("quoted").unwrap().as_str(), Some("42"));
}
#[test]
fn test_seq_iter() {
let doc = Document::parse_str("- 1\n- 2\n- 3").unwrap();
let root = doc.root_value().unwrap();
let sum: i64 = root.seq_iter().filter_map(|v| v.as_i64()).sum();
assert_eq!(sum, 6);
}
#[test]
fn test_map_iter() {
let doc = Document::parse_str("a: 1\nb: 2").unwrap();
let root = doc.root_value().unwrap();
let keys: Vec<&str> = root.map_iter().filter_map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, vec!["a", "b"]);
}
#[test]
fn test_nested_access() {
let doc = Document::parse_str("outer:\n inner:\n value: 42").unwrap();
let root = doc.root_value().unwrap();
let value = root
.get("outer")
.unwrap()
.get("inner")
.unwrap()
.get("value")
.unwrap();
assert_eq!(value.as_i64(), Some(42));
}
#[test]
fn test_type_checks() {
let doc = Document::parse_str("scalar: hello\nseq: [1]\nmap: {a: 1}").unwrap();
let root = doc.root_value().unwrap();
assert!(root.get("scalar").unwrap().is_scalar());
assert!(!root.get("scalar").unwrap().is_sequence());
assert!(!root.get("scalar").unwrap().is_mapping());
assert!(root.get("seq").unwrap().is_sequence());
assert!(!root.get("seq").unwrap().is_scalar());
assert!(root.get("map").unwrap().is_mapping());
assert!(!root.get("map").unwrap().is_scalar());
}
#[test]
fn test_tag_access() {
let doc = Document::parse_str("!custom tagged").unwrap();
let root = doc.root_value().unwrap();
assert!(root.tag().is_some());
assert!(root.tag().unwrap().contains("custom"));
}
#[test]
fn test_no_tag() {
let doc = Document::parse_str("untagged").unwrap();
let root = doc.root_value().unwrap();
assert!(root.tag().is_none());
}
}