use fyaml::{Document, FyParser, NodeStyle, NodeType};
#[test]
fn parse_document_from_string() {
let doc = Document::parse_str("foo: bar").unwrap();
let root = doc.root();
assert!(root.is_some());
}
#[test]
fn parse_document_root_node_is_mapping() {
let doc = Document::parse_str("foo: bar\nbaz: qux").unwrap();
let root = doc.root().unwrap();
assert!(root.is_mapping());
assert_eq!(root.kind(), NodeType::Mapping);
}
#[test]
fn parse_empty_document_returns_error() {
let result = Document::parse_str("");
assert!(result.is_err());
}
#[test]
fn document_emit() {
let doc = Document::parse_str("foo: bar").unwrap();
let output = doc.emit().unwrap();
assert!(output.contains("foo"));
assert!(output.contains("bar"));
}
#[test]
fn node_type_scalar() {
let doc = Document::parse_str("hello").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.kind(), NodeType::Scalar);
assert!(root.is_scalar());
assert!(!root.is_mapping());
assert!(!root.is_sequence());
}
#[test]
fn node_type_mapping() {
let doc = Document::parse_str("foo: bar\nbaz: qux").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.kind(), NodeType::Mapping);
assert!(root.is_mapping());
assert!(!root.is_scalar());
assert!(!root.is_sequence());
}
#[test]
fn node_type_sequence() {
let doc = Document::parse_str("- one\n- two\n- three").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.kind(), NodeType::Sequence);
assert!(root.is_sequence());
assert!(!root.is_scalar());
assert!(!root.is_mapping());
}
#[test]
fn at_path_simple_key() {
let doc = Document::parse_str("foo: bar").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("/foo").unwrap();
assert_eq!(node.scalar_str().unwrap(), "bar");
}
#[test]
fn at_path_nested() {
let doc = Document::parse_str("database:\n host: localhost\n port: 5432").unwrap();
let root = doc.root().unwrap();
let host = root.at_path("/database/host").unwrap();
assert_eq!(host.scalar_str().unwrap(), "localhost");
let port = root.at_path("/database/port").unwrap();
assert_eq!(port.scalar_str().unwrap(), "5432");
}
#[test]
fn at_path_sequence_index() {
let doc = Document::parse_str("items:\n - first\n - second\n - third").unwrap();
let root = doc.root().unwrap();
let first = root.at_path("/items/0").unwrap();
assert_eq!(first.scalar_str().unwrap(), "first");
let second = root.at_path("/items/1").unwrap();
assert_eq!(second.scalar_str().unwrap(), "second");
}
#[test]
fn at_path_not_found() {
let doc = Document::parse_str("foo: bar").unwrap();
let root = doc.root().unwrap();
let result = root.at_path("/nonexistent");
assert!(result.is_none());
}
#[test]
fn at_path_empty_returns_self() {
let doc = Document::parse_str("foo: bar").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("").unwrap();
assert!(node.is_mapping());
}
#[test]
fn scalar_str_zero_copy() {
let doc = Document::parse_str("value: hello world").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("/value").unwrap();
let s: &str = node.scalar_str().unwrap();
assert_eq!(s, "hello world");
}
#[test]
fn scalar_bytes_zero_copy() {
let doc = Document::parse_str("data: bytes").unwrap();
let node = doc.at_path("/data").unwrap();
let bytes: &[u8] = node.scalar_bytes().unwrap();
assert_eq!(bytes, b"bytes");
}
#[test]
fn scalar_preserves_content() {
let doc = Document::parse_str("value: \"bar: wiz\"").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("/value").unwrap();
assert_eq!(node.scalar_str().unwrap(), "bar: wiz");
}
#[test]
fn emit_yaml_formatted() {
let doc = Document::parse_str("value: \"bar: wiz\"").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("/value").unwrap();
assert_eq!(node.emit().unwrap(), "\"bar: wiz\"");
}
#[test]
fn map_iter_yields_key_value_pairs() {
let doc = Document::parse_str("a: 1\nb: 2\nc: 3").unwrap();
let root = doc.root().unwrap();
let pairs: Vec<_> = root.map_iter().collect();
assert_eq!(pairs.len(), 3);
let (key, value) = pairs[0];
assert_eq!(key.scalar_str().unwrap(), "a");
assert_eq!(value.scalar_str().unwrap(), "1");
}
#[test]
fn map_iter_keys_extraction() {
let doc = Document::parse_str("foo: 1\nbar: 2\nbaz: 3").unwrap();
let root = doc.root().unwrap();
let keys: Vec<&str> = root
.map_iter()
.map(|(k, _)| k.scalar_str().unwrap())
.collect();
assert_eq!(keys, vec!["foo", "bar", "baz"]);
}
#[test]
fn map_iter_values_extraction() {
let doc = Document::parse_str("foo: one\nbar: two\nbaz: three").unwrap();
let root = doc.root().unwrap();
let values: Vec<&str> = root
.map_iter()
.map(|(_, v)| v.scalar_str().unwrap())
.collect();
assert_eq!(values, vec!["one", "two", "three"]);
}
#[test]
fn map_get_lookup() {
let doc = Document::parse_str("name: Alice\nage: 30").unwrap();
let root = doc.root().unwrap();
let name = root.map_get("name").unwrap();
assert_eq!(name.scalar_str().unwrap(), "Alice");
let age = root.map_get("age").unwrap();
assert_eq!(age.scalar_str().unwrap(), "30");
assert!(root.map_get("nonexistent").is_none());
}
#[test]
fn seq_iter_yields_nodes() {
let doc = Document::parse_str("- alpha\n- beta\n- gamma").unwrap();
let root = doc.root().unwrap();
let items: Vec<&str> = root.seq_iter().map(|n| n.scalar_str().unwrap()).collect();
assert_eq!(items, vec!["alpha", "beta", "gamma"]);
}
#[test]
fn seq_get_by_index() {
let doc = Document::parse_str("[a, b, c]").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.seq_get(0).unwrap().scalar_str().unwrap(), "a");
assert_eq!(root.seq_get(1).unwrap().scalar_str().unwrap(), "b");
assert_eq!(root.seq_get(2).unwrap().scalar_str().unwrap(), "c");
assert!(root.seq_get(3).is_none());
}
#[test]
fn seq_get_negative_index() {
let doc = Document::parse_str("[a, b, c]").unwrap();
let root = doc.root().unwrap();
let last = root.seq_get(-1).unwrap();
assert_eq!(last.scalar_str().unwrap(), "c");
}
#[test]
fn map_len_returns_count() {
let doc = Document::parse_str("a: 1\nb: 2\nc: 3\nd: 4").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.map_len().unwrap(), 4);
}
#[test]
fn seq_len_returns_count() {
let doc = Document::parse_str("- one\n- two\n- three").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.seq_len().unwrap(), 3);
}
#[test]
fn map_len_empty() {
let doc = Document::parse_str("{}").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.map_len().unwrap(), 0);
}
#[test]
fn seq_len_empty() {
let doc = Document::parse_str("[]").unwrap();
let root = doc.root().unwrap();
assert_eq!(root.seq_len().unwrap(), 0);
}
#[test]
fn get_tag_none_for_untagged() {
let doc = Document::parse_str("value: hello").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("/value").unwrap();
assert!(node.tag_str().unwrap().is_none());
}
#[test]
fn get_tag_returns_tag() {
let doc = Document::parse_str("value: !custom tagged_value").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("/value").unwrap();
let tag = node.tag_str().unwrap();
assert!(tag.is_some());
assert!(tag.unwrap().contains("custom"));
}
#[test]
fn node_style_detects_single_quotes() {
let doc = Document::parse_str("key: 'quoted value'").unwrap();
let root = doc.root().unwrap();
let value = root.at_path("/key").unwrap();
assert_eq!(value.style(), NodeStyle::SingleQuoted);
assert!(value.is_quoted());
}
#[test]
fn node_style_detects_double_quotes() {
let doc = Document::parse_str(r#"key: "quoted value""#).unwrap();
let root = doc.root().unwrap();
let value = root.at_path("/key").unwrap();
assert_eq!(value.style(), NodeStyle::DoubleQuoted);
assert!(value.is_quoted());
}
#[test]
fn node_style_detects_plain_scalar() {
let doc = Document::parse_str("key: plain value").unwrap();
let root = doc.root().unwrap();
let value = root.at_path("/key").unwrap();
assert_eq!(value.style(), NodeStyle::Plain);
assert!(!value.is_quoted());
}
#[test]
fn is_non_plain_detects_quoted() {
let doc = Document::parse_str("single: 'value'\ndouble: \"value\"\nplain: value").unwrap();
let root = doc.root().unwrap();
assert!(root.at_path("/single").unwrap().is_non_plain());
assert!(root.at_path("/double").unwrap().is_non_plain());
assert!(!root.at_path("/plain").unwrap().is_non_plain());
}
#[test]
fn editor_set_yaml_at() {
let mut doc = Document::parse_str("name: Alice").unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/name", "'Bob'").unwrap();
}
let root = doc.root().unwrap();
assert_eq!(root.at_path("/name").unwrap().scalar_str().unwrap(), "Bob");
}
#[test]
fn editor_set_yaml_preserves_quotes() {
let mut doc = Document::parse_str("name: Alice").unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/name", "'true'").unwrap();
}
let root = doc.root().unwrap();
let name = root.at_path("/name").unwrap();
assert_eq!(name.scalar_str().unwrap(), "true");
assert!(name.is_quoted());
assert_eq!(name.style(), NodeStyle::SingleQuoted);
}
#[test]
fn editor_set_yaml_nested() {
let mut doc = Document::parse_str("user:\n name: Alice\n age: 30").unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/user/name", "Bob").unwrap();
}
let root = doc.root().unwrap();
assert_eq!(
root.at_path("/user/name").unwrap().scalar_str().unwrap(),
"Bob"
);
assert_eq!(
root.at_path("/user/age").unwrap().scalar_str().unwrap(),
"30"
);
}
#[test]
fn editor_delete_at() {
let mut doc = Document::parse_str("a: 1\nb: 2\nc: 3").unwrap();
{
let mut ed = doc.edit();
ed.delete_at("/b").unwrap();
}
let root = doc.root().unwrap();
assert_eq!(root.map_len().unwrap(), 2);
assert!(root.at_path("/a").is_some());
assert!(root.at_path("/b").is_none());
assert!(root.at_path("/c").is_some());
}
#[test]
fn editor_build_and_set_root() {
let mut doc = Document::new().unwrap();
{
let mut ed = doc.edit();
let mapping = ed.build_from_yaml("name: Alice\nage: 30").unwrap();
ed.set_root(mapping).unwrap();
}
let root = doc.root().unwrap();
assert!(root.is_mapping());
assert_eq!(
root.at_path("/name").unwrap().scalar_str().unwrap(),
"Alice"
);
}
#[test]
fn traverse_and_extract_pattern() {
let yaml = indoc::indoc! {"
database:
host: localhost
port: 5432
credentials:
user: admin
pass: secret
"};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let user = root.at_path("/database/credentials/user").unwrap();
assert_eq!(user.scalar_str().unwrap(), "admin");
}
#[test]
fn iterate_mapping_keys_values_pattern() {
let yaml = indoc::indoc! {"
config:
debug: true
timeout: 30
name: myapp
"};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let config = root.at_path("/config").unwrap();
assert!(config.is_mapping());
assert_eq!(config.map_len().unwrap(), 3);
let mut keys = Vec::new();
for (k, _) in config.map_iter() {
keys.push(k.scalar_str().unwrap());
}
assert_eq!(keys, vec!["debug", "timeout", "name"]);
}
#[test]
fn iterate_sequence_pattern() {
let yaml = indoc::indoc! {"
servers:
- host: server1
port: 8080
- host: server2
port: 8081
"};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let servers = root.at_path("/servers").unwrap();
assert!(servers.is_sequence());
assert_eq!(servers.seq_len().unwrap(), 2);
let hosts: Vec<&str> = servers
.seq_iter()
.filter_map(|n| n.at_path("/host"))
.map(|n| n.scalar_str().unwrap())
.collect();
assert_eq!(hosts, vec!["server1", "server2"]);
}
#[test]
fn deeply_nested_structure() {
let yaml = indoc::indoc! {"
level1:
level2:
level3:
level4:
value: deep
"};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let deep = root.at_path("/level1/level2/level3/level4/value").unwrap();
assert_eq!(deep.scalar_str().unwrap(), "deep");
}
#[test]
fn mixed_sequence_and_mapping() {
let yaml = indoc::indoc! {"
users:
- name: alice
roles:
- admin
- user
- name: bob
roles:
- user
"};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let alice_admin = root.at_path("/users/0/roles/0").unwrap();
assert_eq!(alice_admin.scalar_str().unwrap(), "admin");
let bob_roles = root.at_path("/users/1/roles").unwrap();
assert_eq!(bob_roles.seq_len().unwrap(), 1);
}
#[test]
fn special_characters_in_values() {
let yaml = indoc::indoc! {r#"
url: "https://example.com:8080/path"
message: "Hello: World"
quoted: 'single quoted'
"#};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let url = root.at_path("/url").unwrap();
assert_eq!(url.scalar_str().unwrap(), "https://example.com:8080/path");
let message = root.at_path("/message").unwrap();
assert_eq!(message.scalar_str().unwrap(), "Hello: World");
}
#[test]
fn multiline_strings() {
let yaml = indoc::indoc! {"
literal: |
line one
line two
folded: >
folded
text
"};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let literal = root.at_path("/literal").unwrap();
let content = literal.scalar_str().unwrap();
assert!(content.contains("line one"));
assert!(content.contains("line two"));
}
#[test]
fn numeric_values() {
let yaml = indoc::indoc! {"
integer: 42
float: 3.14
negative: -10
string_num: \"42\"
"};
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
assert!(root.at_path("/integer").unwrap().is_scalar());
assert!(root.at_path("/float").unwrap().is_scalar());
assert!(root.at_path("/negative").unwrap().is_scalar());
assert!(root.at_path("/string_num").unwrap().is_scalar());
assert_eq!(
root.at_path("/integer").unwrap().scalar_str().unwrap(),
"42"
);
assert_eq!(
root.at_path("/float").unwrap().scalar_str().unwrap(),
"3.14"
);
}
#[test]
fn read_then_edit_then_read() {
let mut doc = Document::parse_str("name: Alice\nage: 30").unwrap();
{
let root = doc.root().unwrap();
assert_eq!(
root.at_path("/name").unwrap().scalar_str().unwrap(),
"Alice"
);
}
{
let mut ed = doc.edit();
ed.set_yaml_at("/name", "Bob").unwrap();
}
{
let root = doc.root().unwrap();
assert_eq!(root.at_path("/name").unwrap().scalar_str().unwrap(), "Bob");
assert_eq!(root.at_path("/age").unwrap().scalar_str().unwrap(), "30");
}
}
#[test]
fn multiple_noderef_same_time() {
let doc = Document::parse_str("a: 1\nb: 2\nc: 3").unwrap();
let root = doc.root().unwrap();
let a = root.at_path("/a").unwrap();
let b = root.at_path("/b").unwrap();
let c = root.at_path("/c").unwrap();
assert_eq!(a.scalar_str().unwrap(), "1");
assert_eq!(b.scalar_str().unwrap(), "2");
assert_eq!(c.scalar_str().unwrap(), "3");
}
#[test]
fn scalar_str_on_mapping_returns_error() {
let doc = Document::parse_str("a: 1").unwrap();
let root = doc.root().unwrap();
assert!(root.scalar_str().is_err());
}
#[test]
fn seq_len_on_mapping_returns_error() {
let doc = Document::parse_str("a: 1").unwrap();
let root = doc.root().unwrap();
assert!(root.seq_len().is_err());
}
#[test]
fn map_len_on_sequence_returns_error() {
let doc = Document::parse_str("[1, 2, 3]").unwrap();
let root = doc.root().unwrap();
assert!(root.map_len().is_err());
}
#[test]
fn empty_document_root_is_none() {
let doc = Document::new().unwrap();
assert!(doc.root().is_none());
}
#[test]
fn document_at_path_on_empty_is_none() {
let doc = Document::new().unwrap();
assert!(doc.at_path("/anything").is_none());
}
#[test]
fn copy_node_between_documents() {
let src = Document::parse_str("name: Alice\nage: 30").unwrap();
let src_root = src.root().unwrap();
let mut dest = Document::new().unwrap();
{
let mut ed = dest.edit();
let copied = ed.copy_node(src_root).unwrap();
ed.set_root(copied).unwrap();
}
let dest_root = dest.root().unwrap();
assert!(dest_root.is_mapping());
assert_eq!(
dest_root.at_path("/name").unwrap().scalar_str().unwrap(),
"Alice"
);
assert_eq!(
dest_root.at_path("/age").unwrap().scalar_str().unwrap(),
"30"
);
let src_root = src.root().unwrap();
assert_eq!(
src_root.at_path("/name").unwrap().scalar_str().unwrap(),
"Alice"
);
}
#[test]
fn copy_node_preserves_structure() {
let src_yaml = indoc::indoc! {"
nested:
key: value
list:
- item1
- item2
"};
let src = Document::parse_str(src_yaml).unwrap();
let src_root = src.root().unwrap();
let mut dest = Document::new().unwrap();
{
let mut ed = dest.edit();
let copied = ed.copy_node(src_root).unwrap();
ed.set_root(copied).unwrap();
}
let dest_root = dest.root().unwrap();
assert_eq!(
dest_root
.at_path("/nested/key")
.unwrap()
.scalar_str()
.unwrap(),
"value"
);
assert_eq!(
dest_root
.at_path("/nested/list")
.unwrap()
.seq_len()
.unwrap(),
2
);
assert_eq!(
dest_root
.at_path("/nested/list/1")
.unwrap()
.scalar_str()
.unwrap(),
"item2"
);
}
#[test]
fn copy_node_preserves_style() {
let src = Document::parse_str("key: 'single quoted'").unwrap();
let src_value = src.root().unwrap().at_path("/key").unwrap();
let mut dest = Document::new().unwrap();
{
let mut ed = dest.edit();
let copied = ed.copy_node(src_value).unwrap();
ed.set_root(copied).unwrap();
}
let dest_root = dest.root().unwrap();
assert_eq!(dest_root.style(), NodeStyle::SingleQuoted);
}
#[test]
fn copy_scalar_between_documents() {
let src = Document::parse_str("just a scalar").unwrap();
let src_root = src.root().unwrap();
let mut dest = Document::new().unwrap();
{
let mut ed = dest.edit();
let copied = ed.copy_node(src_root).unwrap();
ed.set_root(copied).unwrap();
}
let dest_root = dest.root().unwrap();
assert!(dest_root.is_scalar());
assert_eq!(dest_root.scalar_str().unwrap(), "just a scalar");
}
#[test]
fn copy_sequence_between_documents() {
let src = Document::parse_str("[a, b, c]").unwrap();
let src_root = src.root().unwrap();
let mut dest = Document::new().unwrap();
{
let mut ed = dest.edit();
let copied = ed.copy_node(src_root).unwrap();
ed.set_root(copied).unwrap();
}
let dest_root = dest.root().unwrap();
assert!(dest_root.is_sequence());
assert_eq!(dest_root.seq_len().unwrap(), 3);
assert_eq!(dest_root.seq_get(0).unwrap().scalar_str().unwrap(), "a");
}
#[test]
fn copy_subnode_to_other_document() {
let src = Document::parse_str("outer:\n inner:\n deep: value").unwrap();
let inner = src.root().unwrap().at_path("/outer/inner").unwrap();
let mut dest = Document::new().unwrap();
{
let mut ed = dest.edit();
let copied = ed.copy_node(inner).unwrap();
ed.set_root(copied).unwrap();
}
let dest_root = dest.root().unwrap();
assert!(dest_root.is_mapping());
assert_eq!(
dest_root.at_path("/deep").unwrap().scalar_str().unwrap(),
"value"
);
assert!(dest_root.at_path("/outer").is_none()); }
#[test]
fn copy_within_same_document_requires_separate_phases() {
let mut doc = Document::parse_str("original: value").unwrap();
{
let ed = doc.edit();
let root = ed.root().unwrap();
let value = root.at_path("/original").unwrap().scalar_str().unwrap();
assert_eq!(value, "value");
}
assert_eq!(
doc.at_path("/original").unwrap().scalar_str().unwrap(),
"value"
);
}
#[test]
fn zero_copy_scalar_str_pointer_provenance() {
let yaml_content = "key: hello_world_test_value";
let doc = Document::parse_str(yaml_content).unwrap();
let node = doc.at_path("/key").unwrap();
let scalar = node.scalar_str().unwrap();
assert_eq!(scalar, "hello_world_test_value");
let scalar2 = node.scalar_str().unwrap();
assert_eq!(scalar.as_ptr(), scalar2.as_ptr());
}
#[test]
fn zero_copy_scalar_bytes_pointer_provenance() {
let doc = Document::parse_str("data: binary_like_content").unwrap();
let node = doc.at_path("/data").unwrap();
let bytes1 = node.scalar_bytes().unwrap();
let bytes2 = node.scalar_bytes().unwrap();
assert_eq!(bytes1.as_ptr(), bytes2.as_ptr());
assert_eq!(bytes1, b"binary_like_content");
}
#[test]
fn zero_copy_tag_str_pointer_provenance() {
let doc = Document::parse_str("!custom tagged_value").unwrap();
let root = doc.root().unwrap();
let tag1 = root.tag_str().unwrap();
let tag2 = root.tag_str().unwrap();
assert!(tag1.is_some());
assert_eq!(tag1.unwrap(), "!custom");
assert_eq!(tag1.unwrap().as_ptr(), tag2.unwrap().as_ptr());
}
#[test]
fn zero_copy_iteration_no_string_allocation() {
let doc = Document::parse_str(
r#"
items:
- first
- second
- third
"#,
)
.unwrap();
let items = doc.at_path("/items").unwrap();
let pointers1: Vec<*const u8> = items
.seq_iter()
.map(|n| n.scalar_str().unwrap().as_ptr())
.collect();
let pointers2: Vec<*const u8> = items
.seq_iter()
.map(|n| n.scalar_str().unwrap().as_ptr())
.collect();
assert_eq!(pointers1, pointers2);
}
#[test]
fn zero_copy_map_iteration() {
let doc = Document::parse_str(
r#"
key1: value1
key2: value2
"#,
)
.unwrap();
let root = doc.root().unwrap();
let pairs1: Vec<(*const u8, *const u8)> = root
.map_iter()
.map(|(k, v)| {
(
k.scalar_str().unwrap().as_ptr(),
v.scalar_str().unwrap().as_ptr(),
)
})
.collect();
let pairs2: Vec<(*const u8, *const u8)> = root
.map_iter()
.map(|(k, v)| {
(
k.scalar_str().unwrap().as_ptr(),
v.scalar_str().unwrap().as_ptr(),
)
})
.collect();
assert_eq!(pairs1, pairs2);
}
#[test]
fn stream_documents_outlive_parser() {
let docs: Vec<Document>;
{
let parser = FyParser::from_string("---\ndoc1: v1\n---\ndoc2: v2\n---\ndoc3: v3").unwrap();
docs = parser.doc_iter().filter_map(|r| r.ok()).collect();
}
assert_eq!(docs.len(), 3);
assert_eq!(
docs[0].at_path("/doc1").unwrap().scalar_str().unwrap(),
"v1"
);
assert_eq!(
docs[1].at_path("/doc2").unwrap().scalar_str().unwrap(),
"v2"
);
assert_eq!(
docs[2].at_path("/doc3").unwrap().scalar_str().unwrap(),
"v3"
);
}
#[test]
fn stream_documents_outlive_iterator() {
let parser = FyParser::from_string("---\nkey: value1\n---\nkey: value2").unwrap();
let docs: Vec<Document>;
{
let iter = parser.doc_iter();
docs = iter.filter_map(|r| r.ok()).collect();
}
assert_eq!(docs.len(), 2);
assert_eq!(
docs[0].at_path("/key").unwrap().scalar_str().unwrap(),
"value1"
);
assert_eq!(
docs[1].at_path("/key").unwrap().scalar_str().unwrap(),
"value2"
);
}
#[test]
fn stream_documents_concurrent_access() {
let parser = FyParser::from_string("---\na: 1\n---\nb: 2\n---\nc: 3").unwrap();
let docs: Vec<Document> = parser.doc_iter().filter_map(|r| r.ok()).collect();
let values: Vec<&str> = vec![
docs[0].at_path("/a").unwrap().scalar_str().unwrap(),
docs[1].at_path("/b").unwrap().scalar_str().unwrap(),
docs[2].at_path("/c").unwrap().scalar_str().unwrap(),
];
assert_eq!(values, vec!["1", "2", "3"]);
}
#[test]
fn stream_documents_zero_copy() {
let parser = FyParser::from_string("---\nkey: stream_value").unwrap();
let docs: Vec<Document> = parser.doc_iter().filter_map(|r| r.ok()).collect();
let node = docs[0].at_path("/key").unwrap();
let ptr1 = node.scalar_str().unwrap().as_ptr();
let ptr2 = node.scalar_str().unwrap().as_ptr();
assert_eq!(ptr1, ptr2);
}
#[test]
fn stream_documents_are_independent() {
let parser = FyParser::from_string("---\nshared: doc1_value\n---\nshared: doc2_value").unwrap();
let mut iter = parser.doc_iter();
let mut doc1 = iter.next().unwrap().unwrap();
let doc2 = iter.next().unwrap().unwrap();
assert_eq!(
doc1.at_path("/shared").unwrap().scalar_str().unwrap(),
"doc1_value"
);
assert_eq!(
doc2.at_path("/shared").unwrap().scalar_str().unwrap(),
"doc2_value"
);
{
let mut ed = doc1.edit();
ed.set_yaml_at("/shared", "'modified'").unwrap();
}
assert_eq!(
doc2.at_path("/shared").unwrap().scalar_str().unwrap(),
"doc2_value"
);
}
#[test]
fn stream_parser_reusable() {
let parser = FyParser::from_string("single: document").unwrap();
let docs1: Vec<Document> = parser.doc_iter().filter_map(|r| r.ok()).collect();
assert_eq!(docs1.len(), 1);
let docs2: Vec<Document> = parser.doc_iter().filter_map(|r| r.ok()).collect();
assert_eq!(docs2.len(), 0);
}
#[test]
fn editor_build_from_yaml_invalid_yaml_no_leak() {
let mut doc = Document::new().unwrap();
for _ in 0..100 {
let mut ed = doc.edit();
let result = ed.build_from_yaml("[unclosed bracket");
assert!(result.is_err());
}
}
#[test]
fn editor_build_from_yaml_restores_state_after_failure() {
let mut doc = Document::parse_str("existing: value").unwrap();
{
let mut ed = doc.edit();
let result = ed.build_from_yaml("[invalid");
assert!(result.is_err());
}
let root = doc.root().unwrap();
assert_eq!(
root.at_path("/existing").unwrap().scalar_str().unwrap(),
"value"
);
{
let mut ed = doc.edit();
let node = ed.build_from_yaml("new: data").unwrap();
ed.set_yaml_at("/added", "works").unwrap();
drop(node);
}
let root = doc.root().unwrap();
assert_eq!(
root.at_path("/added").unwrap().scalar_str().unwrap(),
"works"
);
}
#[test]
fn editor_build_from_yaml_error_has_location() {
let mut doc = Document::new().unwrap();
let mut ed = doc.edit();
let result = ed.build_from_yaml("key: value\n[invalid");
match result {
Err(fyaml::Error::ParseError(pe)) => {
assert!(pe.line().is_some(), "Editor parse error should have line");
}
Err(other) => panic!("Expected ParseError, got {:?}", other),
Ok(_) => panic!("Expected parse error for invalid YAML"),
}
}
#[test]
fn editor_raw_node_handle_dropped_without_insert() {
let mut doc = Document::new().unwrap();
for _ in 0..100 {
let mut ed = doc.edit();
let _node = ed.build_from_yaml("key: value").unwrap();
}
}
#[test]
fn editor_raw_node_handle_scalar_dropped() {
let mut doc = Document::new().unwrap();
for _ in 0..100 {
let mut ed = doc.edit();
let _scalar = ed.build_scalar("test value").unwrap();
}
}
#[test]
fn editor_raw_node_handle_sequence_dropped() {
let mut doc = Document::new().unwrap();
for _ in 0..100 {
let mut ed = doc.edit();
let _seq = ed.build_sequence().unwrap();
}
}
#[test]
fn editor_raw_node_handle_mapping_dropped() {
let mut doc = Document::new().unwrap();
for _ in 0..100 {
let mut ed = doc.edit();
let _map = ed.build_mapping().unwrap();
}
}
#[test]
fn stream_parse_error_returns_err() {
let parser = FyParser::from_string("[unclosed").unwrap();
let results: Vec<_> = parser.doc_iter().collect();
assert!(!results.is_empty());
assert!(results.iter().any(|r| r.is_err()));
}
#[test]
fn stream_parse_error_has_location() {
let parser = FyParser::from_string("[unclosed").unwrap();
let results: Vec<_> = parser.doc_iter().collect();
let err = results
.into_iter()
.find(|r| r.is_err())
.unwrap()
.unwrap_err();
match err {
fyaml::Error::ParseError(pe) => {
assert!(
pe.line().is_some(),
"Stream parse error should have line number"
);
assert!(
pe.column().is_some(),
"Stream parse error should have column number"
);
assert!(
!pe.message().is_empty(),
"Stream parse error should have a message"
);
}
other => panic!("Expected ParseError variant, got: {:?}", other),
}
}
#[test]
fn stream_parse_error_multiline_location() {
let yaml = "---\nkey: value\n[unclosed";
let parser = FyParser::from_string(yaml).unwrap();
let results: Vec<_> = parser.doc_iter().collect();
let err = results.into_iter().find(|r| r.is_err());
assert!(err.is_some(), "Should have produced a parse error");
let err = err.unwrap().unwrap_err();
if let fyaml::Error::ParseError(pe) = err {
let line = pe.line().expect("Should have line number");
assert!(line >= 2, "Error line should be >= 2, got: {}", line);
}
}
#[test]
fn stream_empty_yields_none_not_error() {
let parser = FyParser::from_string("").unwrap();
let results: Vec<_> = parser.doc_iter().collect();
assert!(results.is_empty());
}
#[test]
fn stream_valid_then_invalid() {
let parser = FyParser::from_string("---\nvalid: doc\n---\n[unclosed").unwrap();
let results: Vec<_> = parser.doc_iter().collect();
let valid_count = results.iter().filter(|r| r.is_ok()).count();
let error_count = results.iter().filter(|r| r.is_err()).count();
assert!(valid_count >= 1, "Should have at least one valid document");
assert!(error_count >= 1, "Should have at least one error");
}
#[test]
fn tag_yaml_standard_types() {
let yaml = "
int: !!int 42
str: !!str 42
bool: !!bool true
float: !!float 3.14
null: !!null
";
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
let int_node = root.at_path("/int").unwrap();
let tag = int_node.tag_str().unwrap();
assert!(tag.is_some());
assert!(tag.unwrap().contains("int"));
let str_node = root.at_path("/str").unwrap();
let tag = str_node.tag_str().unwrap();
assert!(tag.is_some());
assert!(tag.unwrap().contains("str"));
}
#[test]
fn tag_custom_local() {
let doc = Document::parse_str("!mytag custom_value").unwrap();
let root = doc.root().unwrap();
let tag = root.tag_str().unwrap();
assert!(tag.is_some());
assert!(tag.unwrap().contains("mytag"));
assert_eq!(root.scalar_str().unwrap(), "custom_value");
}
#[test]
fn tag_on_mapping() {
let doc = Document::parse_str("!person\nname: Alice\nage: 30").unwrap();
let root = doc.root().unwrap();
let tag = root.tag_str().unwrap();
assert!(tag.is_some());
assert!(tag.unwrap().contains("person"));
assert!(root.is_mapping());
}
#[test]
fn tag_on_sequence() {
let doc = Document::parse_str("!list\n- item1\n- item2").unwrap();
let root = doc.root().unwrap();
let tag = root.tag_str().unwrap();
assert!(tag.is_some());
assert!(tag.unwrap().contains("list"));
assert!(root.is_sequence());
}
#[test]
fn utf8_content_preserved() {
let doc = Document::parse_str("greeting: こんにちは").unwrap();
let root = doc.root().unwrap();
let greeting = root.at_path("/greeting").unwrap();
assert_eq!(greeting.scalar_str().unwrap(), "こんにちは");
}
#[test]
fn emoji_content_preserved() {
let doc = Document::parse_str("emoji: 🎉🎊🎁").unwrap();
let root = doc.root().unwrap();
let emoji = root.at_path("/emoji").unwrap();
assert_eq!(emoji.scalar_str().unwrap(), "🎉🎊🎁");
}
#[test]
fn mixed_utf8_content() {
let doc = Document::parse_str("message: \"Hello 世界! Привет мир! مرحبا بالعالم\"").unwrap();
let root = doc.root().unwrap();
let message = root.at_path("/message").unwrap();
assert!(message.scalar_str().unwrap().contains("世界"));
assert!(message.scalar_str().unwrap().contains("Привет"));
assert!(message.scalar_str().unwrap().contains("مرحبا"));
}
#[test]
fn special_characters_escaped() {
let doc = Document::parse_str("text: \"line1\\nline2\\ttabbed\"").unwrap();
let root = doc.root().unwrap();
let text = root.at_path("/text").unwrap();
let content = text.scalar_str().unwrap();
assert!(content.contains('\n'));
assert!(content.contains('\t'));
}
#[test]
fn long_utf8_string() {
let long_string = "日本語".repeat(1000);
let yaml = format!("content: \"{}\"", long_string);
let doc = Document::parse_str(&yaml).unwrap();
let root = doc.root().unwrap();
let content = root.at_path("/content").unwrap();
assert_eq!(content.scalar_str().unwrap(), long_string);
}
#[test]
fn value_ref_basic_access() {
let doc = Document::parse_str("name: Alice\nage: 30\nactive: true").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("name").unwrap().as_str(), Some("Alice"));
assert_eq!(root.get("age").unwrap().as_i64(), Some(30));
assert_eq!(root.get("active").unwrap().as_bool(), Some(true));
}
#[test]
fn value_ref_quoted_not_interpreted() {
let doc = Document::parse_str("quoted: 'true'\nquoted_num: '42'").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("quoted_num").unwrap().as_i64(), None);
assert_eq!(root.get("quoted_num").unwrap().as_str(), Some("42"));
}
#[test]
fn value_ref_null_detection() {
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 value_ref_number_formats() {
let doc = Document::parse_str("dec: 255\nhex: 0xFF\noct: 0o377\nbin: 0b11111111").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("dec").unwrap().as_i64(), Some(255));
assert_eq!(root.get("hex").unwrap().as_i64(), Some(255));
assert_eq!(root.get("oct").unwrap().as_i64(), Some(255));
assert_eq!(root.get("bin").unwrap().as_i64(), Some(255));
}
#[test]
fn value_ref_special_floats() {
let doc = Document::parse_str("inf: .inf\nneginf: -.inf\nnan: .nan").unwrap();
let root = doc.root_value().unwrap();
assert!(root.get("inf").unwrap().as_f64().unwrap().is_infinite());
assert!(root
.get("inf")
.unwrap()
.as_f64()
.unwrap()
.is_sign_positive());
assert!(root.get("neginf").unwrap().as_f64().unwrap().is_infinite());
assert!(root
.get("neginf")
.unwrap()
.as_f64()
.unwrap()
.is_sign_negative());
assert!(root.get("nan").unwrap().as_f64().unwrap().is_nan());
}
#[test]
fn value_ref_bool_variants() {
let doc =
Document::parse_str("t1: true\nt2: True\nt3: yes\nt4: on\nf1: false\nf2: no\nf3: off")
.unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.get("t1").unwrap().as_bool(), Some(true));
assert_eq!(root.get("t2").unwrap().as_bool(), Some(true));
assert_eq!(root.get("t3").unwrap().as_bool(), Some(true));
assert_eq!(root.get("t4").unwrap().as_bool(), Some(true));
assert_eq!(root.get("f1").unwrap().as_bool(), Some(false));
assert_eq!(root.get("f2").unwrap().as_bool(), Some(false));
assert_eq!(root.get("f3").unwrap().as_bool(), Some(false));
}
#[test]
fn value_ref_sequence_iteration() {
let doc = Document::parse_str("- 10\n- 20\n- 30").unwrap();
let root = doc.root_value().unwrap();
let sum: i64 = root.seq_iter().filter_map(|v| v.as_i64()).sum();
assert_eq!(sum, 60);
}
#[test]
fn value_ref_mapping_iteration() {
let doc = Document::parse_str("a: 1\nb: 2\nc: 3").unwrap();
let root = doc.root_value().unwrap();
let sum: i64 = root.map_iter().filter_map(|(_, v)| v.as_i64()).sum();
assert_eq!(sum, 6);
}
#[test]
fn value_ref_nested_navigation() {
let doc = Document::parse_str("level1:\n level2:\n level3: 42").unwrap();
let root = doc.root_value().unwrap();
let value = root
.get("level1")
.unwrap()
.get("level2")
.unwrap()
.get("level3")
.unwrap();
assert_eq!(value.as_i64(), Some(42));
let value2 = root.at_path("/level1/level2/level3").unwrap();
assert_eq!(value2.as_i64(), Some(42));
}
#[test]
fn value_ref_index_access() {
let doc = Document::parse_str("- first\n- second\n- third").unwrap();
let root = doc.root_value().unwrap();
assert_eq!(root.index(0).unwrap().as_str(), Some("first"));
assert_eq!(root.index(1).unwrap().as_str(), Some("second"));
assert_eq!(root.index(-1).unwrap().as_str(), Some("third")); }
#[test]
fn value_ref_tag_access() {
let doc = Document::parse_str("!custom tagged_value").unwrap();
let root = doc.root_value().unwrap();
assert!(root.tag().is_some());
assert!(root.tag().unwrap().contains("custom"));
}
#[test]
fn value_ref_type_checking() {
let doc = Document::parse_str("scalar: hello\nseq:\n - item\nmap:\n key: value").unwrap();
let root = doc.root_value().unwrap();
assert!(root.get("scalar").unwrap().is_scalar());
assert!(root.get("seq").unwrap().is_sequence());
assert!(root.get("map").unwrap().is_mapping());
assert!(!root.get("scalar").unwrap().is_sequence());
assert!(!root.get("seq").unwrap().is_mapping());
assert!(!root.get("map").unwrap().is_scalar());
}
#[test]
fn test_quote_style_preservation() {
let yaml = "single: 'quoted value'
double: \"another value\"
plain: unquoted
";
let mut doc = Document::parse_str(yaml).unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/plain", "'now single quoted'").unwrap();
}
let output = doc.emit().unwrap();
println!("Output:\n{}", output);
assert!(
output.contains("'quoted value'"),
"Single quotes should be preserved"
);
assert!(
output.contains("\"another value\""),
"Double quotes should be preserved"
);
}
#[test]
fn test_block_style_preservation() {
let yaml = "literal: |
line one
line two
folded: >
folded
text
plain: value
";
let mut doc = Document::parse_str(yaml).unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/plain", "modified").unwrap();
}
let output = doc.emit().unwrap();
println!("Output:\n{}", output);
assert!(
output.contains("literal: |"),
"Literal block style should be preserved"
);
assert!(
output.contains("folded: >"),
"Folded block style should be preserved"
);
}
#[test]
fn test_flow_style_preservation() {
let yaml = "flow_seq: [1, 2, 3]
flow_map: {a: 1, b: 2}
other: value
";
let mut doc = Document::parse_str(yaml).unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/other", "modified").unwrap();
}
let output = doc.emit().unwrap();
println!("Output:\n{}", output);
assert!(
output.contains("[") && output.contains("]"),
"Flow sequence style should be preserved"
);
assert!(
output.contains("{") && output.contains("}"),
"Flow mapping style should be preserved"
);
}
#[test]
fn test_comment_preservation() {
let yaml = "# Top comment
name: Alice # inline comment
age: 30
";
let mut doc = Document::parse_str(yaml).unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/age", "31").unwrap();
}
let output = doc.emit().unwrap();
println!("Output:\n{}", output);
assert!(
output.contains("# Top comment"),
"Top comment should be preserved"
);
assert!(
output.contains("# inline comment"),
"Inline comment should be preserved"
);
}
#[test]
fn destructor_stress_stream_documents() {
for _ in 0..100 {
let parser =
FyParser::from_string("---\nkey: value\n---\nfoo: bar\n---\nbaz: qux").unwrap();
let docs: Vec<_> = parser.doc_iter().filter_map(|r| r.ok()).collect();
assert_eq!(docs.len(), 3);
}
}
#[test]
fn destructor_stress_standalone_documents() {
for i in 0..100 {
let doc = Document::parse_str(&format!("key{}: value{}", i, i)).unwrap();
let root = doc.root().unwrap();
assert_eq!(
root.at_path(&format!("/key{}", i))
.unwrap()
.scalar_str()
.unwrap(),
format!("value{}", i)
);
}
}
#[test]
fn destructor_stress_owned_string() {
for i in 0..100 {
let yaml = format!("owned_key{}: owned_value{}", i, i);
let doc = Document::from_string(yaml).unwrap();
let root = doc.root().unwrap();
assert!(root.is_mapping());
}
}
#[test]
fn destructor_stress_build_from_yaml_error() {
let mut doc = Document::parse_str("key: value").unwrap();
for _ in 0..100 {
let mut ed = doc.edit();
let result = ed.build_from_yaml("[unclosed");
assert!(result.is_err());
}
}
#[test]
fn destructor_stress_delete_at() {
for _ in 0..50 {
let mut doc = Document::parse_str("a: 1\nb: 2\nc: 3\nd: 4").unwrap();
{
let mut ed = doc.edit();
ed.delete_at("/a").unwrap();
ed.delete_at("/b").unwrap();
ed.delete_at("/c").unwrap();
ed.delete_at("/d").unwrap();
}
assert!(doc.root().unwrap().map_iter().next().is_none());
}
}
#[test]
fn from_bytes_basic() {
let yaml_bytes = b"name: Alice\nage: 30".to_vec();
let doc = Document::from_bytes(yaml_bytes).unwrap();
let root = doc.root().unwrap();
assert_eq!(
root.at_path("/name").unwrap().scalar_str().unwrap(),
"Alice"
);
assert_eq!(root.at_path("/age").unwrap().scalar_str().unwrap(), "30");
}
#[test]
fn from_bytes_empty_fails() {
let result = Document::from_bytes(vec![]);
assert!(result.is_err());
}
#[test]
fn from_bytes_zero_copy_access() {
let yaml_bytes = b"key: value".to_vec();
let doc = Document::from_bytes(yaml_bytes).unwrap();
let root = doc.root().unwrap();
let bytes = root.at_path("/key").unwrap().scalar_bytes().unwrap();
assert_eq!(bytes, b"value");
}
#[test]
fn from_bytes_with_utf8() {
let yaml_bytes = "emoji: 🎉\nunicode: café".as_bytes().to_vec();
let doc = Document::from_bytes(yaml_bytes).unwrap();
let root = doc.root().unwrap();
assert_eq!(root.at_path("/emoji").unwrap().scalar_str().unwrap(), "🎉");
assert_eq!(
root.at_path("/unicode").unwrap().scalar_str().unwrap(),
"café"
);
}
#[test]
fn from_bytes_complex_structure() {
let yaml = r#"
database:
host: localhost
port: 5432
users:
- admin
- guest
"#;
let doc = Document::from_bytes(yaml.as_bytes().to_vec()).unwrap();
let root = doc.root().unwrap();
assert_eq!(
root.at_path("/database/host")
.unwrap()
.scalar_str()
.unwrap(),
"localhost"
);
assert_eq!(
root.at_path("/database/port")
.unwrap()
.scalar_str()
.unwrap(),
"5432"
);
assert_eq!(
root.at_path("/database/users/0")
.unwrap()
.scalar_str()
.unwrap(),
"admin"
);
assert_eq!(
root.at_path("/database/users/1")
.unwrap()
.scalar_str()
.unwrap(),
"guest"
);
}
#[test]
fn destructor_stress_from_bytes() {
for i in 0..100 {
let yaml = format!("key{}: value{}", i, i);
let doc = Document::from_bytes(yaml.into_bytes()).unwrap();
let root = doc.root().unwrap();
assert!(root.is_mapping());
}
}