use crate::config;
use crate::document::Document;
use crate::error::{Error, Result};
use crate::ffi_util::take_c_string;
use crate::iter::{MapIter, SeqIter};
use crate::node::{NodeStyle, NodeType};
use fyaml_sys::*;
use libc::size_t;
use std::fmt;
use std::ptr::NonNull;
use std::slice;
#[derive(Clone, Copy)]
pub struct NodeRef<'doc> {
doc: &'doc Document,
node_ptr: NonNull<fy_node>,
}
impl<'doc> NodeRef<'doc> {
#[inline]
pub(crate) fn new(node_ptr: NonNull<fy_node>, doc: &'doc Document) -> Self {
NodeRef { doc, node_ptr }
}
#[inline]
pub(crate) fn as_ptr(&self) -> *mut fy_node {
self.node_ptr.as_ptr()
}
#[inline]
pub fn document(&self) -> &'doc Document {
self.doc
}
#[inline]
pub fn kind(&self) -> NodeType {
unsafe { NodeType::from(fy_node_get_type(self.as_ptr())) }
}
#[inline]
pub fn is_scalar(&self) -> bool {
self.kind() == NodeType::Scalar
}
#[inline]
pub fn is_mapping(&self) -> bool {
self.kind() == NodeType::Mapping
}
#[inline]
pub fn is_sequence(&self) -> bool {
self.kind() == NodeType::Sequence
}
#[inline]
pub fn style(&self) -> NodeStyle {
NodeStyle::from(unsafe { fy_node_get_style(self.as_ptr()) })
}
#[inline]
pub fn is_quoted(&self) -> bool {
let style = unsafe { fy_node_get_style(self.as_ptr()) };
style == FYNS_SINGLE_QUOTED || style == FYNS_DOUBLE_QUOTED
}
#[inline]
pub fn is_non_plain(&self) -> bool {
let style = unsafe { fy_node_get_style(self.as_ptr()) };
style == FYNS_SINGLE_QUOTED
|| style == FYNS_DOUBLE_QUOTED
|| style == FYNS_LITERAL
|| style == FYNS_FOLDED
}
pub fn scalar_bytes(&self) -> Result<&'doc [u8]> {
let mut len: size_t = 0;
let data_ptr = unsafe { fy_node_get_scalar(self.as_ptr(), &mut len) };
if data_ptr.is_null() {
return Err(Error::TypeMismatch {
expected: "scalar",
got: "non-scalar or null",
});
}
if len > isize::MAX as usize {
return Err(Error::ScalarTooLarge(len));
}
Ok(unsafe { slice::from_raw_parts(data_ptr as *const u8, len) })
}
pub fn scalar_str(&self) -> Result<&'doc str> {
let bytes = self.scalar_bytes()?;
std::str::from_utf8(bytes).map_err(Error::from)
}
pub fn tag_bytes(&self) -> Result<Option<&'doc [u8]>> {
let mut len: size_t = 0;
let tag_ptr = unsafe { fy_node_get_tag(self.as_ptr(), &mut len) };
if tag_ptr.is_null() {
return Ok(None);
}
if len > isize::MAX as usize {
return Err(Error::ScalarTooLarge(len));
}
Ok(Some(unsafe {
slice::from_raw_parts(tag_ptr as *const u8, len)
}))
}
pub fn tag_str(&self) -> Result<Option<&'doc str>> {
match self.tag_bytes()? {
Some(bytes) => std::str::from_utf8(bytes).map(Some).map_err(Error::from),
None => Ok(None),
}
}
pub fn at_path(&self, path: &str) -> Option<NodeRef<'doc>> {
let node_ptr =
unsafe { fy_node_by_path(self.as_ptr(), path.as_ptr() as *const i8, path.len(), 0) };
NonNull::new(node_ptr).map(|nn| NodeRef::new(nn, self.doc))
}
pub fn seq_len(&self) -> Result<usize> {
let len: i32 = unsafe { fy_node_sequence_item_count(self.as_ptr()) };
if len < 0 {
return Err(Error::TypeMismatch {
expected: "sequence",
got: "non-sequence",
});
}
Ok(len as usize)
}
pub fn map_len(&self) -> Result<usize> {
let len: i32 = unsafe { fy_node_mapping_item_count(self.as_ptr()) };
if len < 0 {
return Err(Error::TypeMismatch {
expected: "mapping",
got: "non-mapping",
});
}
Ok(len as usize)
}
pub fn seq_get(&self, index: i32) -> Option<NodeRef<'doc>> {
if !self.is_sequence() {
return None;
}
let node_ptr = unsafe { fy_node_sequence_get_by_index(self.as_ptr(), index) };
NonNull::new(node_ptr).map(|nn| NodeRef::new(nn, self.doc))
}
#[inline]
pub fn seq_iter(&self) -> SeqIter<'doc> {
SeqIter::new(*self)
}
pub fn map_get(&self, key: &str) -> Option<NodeRef<'doc>> {
if !self.is_mapping() {
return None;
}
let node_ptr = unsafe {
fy_node_mapping_lookup_by_string(self.as_ptr(), key.as_ptr() as *const i8, key.len())
};
NonNull::new(node_ptr).map(|nn| NodeRef::new(nn, self.doc))
}
#[inline]
pub fn map_iter(&self) -> MapIter<'doc> {
MapIter::new(*self)
}
pub fn emit(&self) -> Result<String> {
let ptr = unsafe { fy_emit_node_to_string(self.as_ptr(), config::emit_flags()) };
if ptr.is_null() {
return Err(Error::Ffi("fy_emit_node_to_string returned null"));
}
Ok(unsafe { take_c_string(ptr) })
}
}
impl fmt::Display for NodeRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.emit() {
Ok(s) => write!(f, "{}", s),
Err(_) => write!(f, "<NodeRef emit error>"),
}
}
}
impl fmt::Debug for NodeRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NodeRef")
.field("kind", &self.kind())
.field("style", &self.style())
.field("ptr", &self.node_ptr)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scalar_str() {
let doc = Document::parse_str("key: value").unwrap();
let node = doc.at_path("/key").unwrap();
assert_eq!(node.scalar_str().unwrap(), "value");
}
#[test]
fn test_is_quoted() {
let doc = Document::parse_str("plain: value\nquoted: 'value'").unwrap();
let plain = doc.at_path("/plain").unwrap();
let quoted = doc.at_path("/quoted").unwrap();
assert!(!plain.is_quoted());
assert!(quoted.is_quoted());
}
#[test]
fn test_navigation() {
let doc = Document::parse_str("a:\n b:\n c: deep").unwrap();
let node = doc.root().unwrap().at_path("/a/b/c").unwrap();
assert_eq!(node.scalar_str().unwrap(), "deep");
}
#[test]
fn test_seq_len() {
let doc = Document::parse_str("[1, 2, 3]").unwrap();
assert_eq!(doc.root().unwrap().seq_len().unwrap(), 3);
}
#[test]
fn test_map_len() {
let doc = Document::parse_str("a: 1\nb: 2").unwrap();
assert_eq!(doc.root().unwrap().map_len().unwrap(), 2);
}
}