| Crates.io | fyaml |
| lib.rs | fyaml |
| version | 0.4.0 |
| created_at | 2026-01-19 07:44:46.421132+00 |
| updated_at | 2026-01-24 04:01:41.316579+00 |
| description | Safe Rust bindings for libfyaml YAML parser with DOM navigation, path queries, and serde-compatible Value type |
| homepage | |
| repository | https://github.com/0k/fyaml |
| max_upload_size | |
| id | 2053962 |
| size | 436,192 |
A safe Rust wrapper around the libfyaml C library for parsing and
manipulating YAML documents.
fyaml provides an idiomatic Rust interface to the high-performance
libfyaml YAML parsing library. It supports DOM-style navigation,
zero-copy scalar access, node type introspection, multi-document
parsing, document mutation, and a serde-compatible Value type.
Early development - This library is functional but has not yet been
widely used or audited. The API may change, and edge cases may exist. If
you need a mature, battle-tested YAML library, consider serde_yml or
serde-yaml-ng instead.
fyaml is built on libfyaml, a
modern C library that offers several advantages over the traditional
libyaml:
This makes fyaml suitable for use cases requiring DOM manipulation,
YAML transformation tools, or configuration inspection utilities where
path-based queries are convenient.
NodeRef and
ValueRef/foo/bar, /list/0)FyParserEditor with compile-time safetyValueRef type: Zero-copy typed access with YAML type
interpretation
as_str(), as_bool(), as_i64(), as_f64(), is_null()Value type: Pure Rust enum with serde support
value["key"], value[0]Parse errors include detailed location information (line and column numbers), making it easy to report errors to users or integrate with IDEs and linters.
use fyaml::Document;
let result = Document::parse_str("[unclosed bracket");
if let Err(e) = result {
// Access structured error info
if let fyaml::Error::ParseError(parse_err) = &e {
println!("Error: {}", parse_err.message());
if let Some((line, col)) = parse_err.location() {
println!("At line {}, column {}", line, col);
}
}
// Or just display it nicely
println!("{}", e); // "Parse error at 2:1: flow sequence without a closing bracket"
}
The ParseError type provides:
message() - The error message from libfyamlline() - Line number (1-based), if availablecolumn() - Column number (1-based), if availablelocation() - Tuple of (line, column) if both availableAll parsing methods (Document::parse_str, Document::from_string,
Document::from_bytes, Editor::build_from_yaml) capture errors
silently without printing to stderr, making the library suitable for use
in GUI applications and test suites.
fyaml leverages libfyaml's zero-copy design for efficient memory
usage. When you access scalar values through NodeRef or ValueRef,
you get references directly into the parsed document buffer - no string
copying or allocation occurs.
use fyaml::Document;
let doc = Document::parse_str("message: Hello, World!").unwrap();
let root = doc.root().unwrap();
let node = root.at_path("/message").unwrap();
// Zero-copy: this &str points directly into the document's memory
let s: &str = node.scalar_str().unwrap();
assert_eq!(s, "Hello, World!");
// The reference is tied to the document's lifetime -
// this prevents use-after-free at compile time
This is particularly beneficial for:
When modifying documents with Editor, fyaml preserves formatting and
comments. This is essential for configuration files where maintaining
the original style improves readability and diff-friendliness.
'value'), double-quoted
("value"), and plain scalars|) and folded (>) blocks[a, b], {a: 1}) vs block (indented)
sequences/mappingsuse fyaml::Document;
// Comments and quote styles are preserved through edits
let yaml = "# Database configuration
database:
host: 'localhost' # local dev server
port: 5432
";
let mut doc = Document::parse_str(yaml).unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/database/port", "5433").unwrap();
}
let output = doc.emit().unwrap();
// Comments preserved
assert!(output.contains("# Database configuration"));
assert!(output.contains("# local dev server"));
// Quote style preserved
assert!(output.contains("'localhost'"));
Block scalars are also preserved:
use fyaml::Document;
let yaml = "script: |
echo hello
echo world
name: test
";
let mut doc = Document::parse_str(yaml).unwrap();
{
let mut ed = doc.edit();
ed.set_yaml_at("/name", "modified").unwrap();
}
let output = doc.emit().unwrap();
// Literal block style (|) is preserved for the script
assert!(output.contains("script: |"));
[a, b], {a: 1}) are preserved as flow style but
may be reformatted across multiple lines by libfyaml's emitterPaths use / as the separator (YPATH/JSON Pointer style):
/key - access a mapping key/0 - access a sequence index/parent/child/0 - nested accessuse fyaml::Document;
let yaml = "
database:
host: localhost
ports:
- 5432
- 5433
";
let doc = Document::parse_str(yaml).unwrap();
let root = doc.root().unwrap();
// Access mapping key
let db = root.at_path("/database").unwrap();
assert!(db.is_mapping());
// Nested access
let host = root.at_path("/database/host").unwrap();
assert_eq!(host.scalar_str().unwrap(), "localhost");
// Sequence index
let first_port = root.at_path("/database/ports/0").unwrap();
assert_eq!(first_port.scalar_str().unwrap(), "5432");
The Value type provides a convenient, serde-compatible way to work
with YAML:
use fyaml::Value;
// Parse YAML
let value: Value = "name: Alice\nage: 30".parse().unwrap();
// Access values with indexing
assert_eq!(value["name"].as_str(), Some("Alice"));
assert_eq!(value["age"].as_i64(), Some(30));
// Emit back to YAML
let yaml = value.to_yaml_string().unwrap();
For more control and zero-copy scalar access, use the Document API:
use fyaml::Document;
let doc = Document::parse_str("database:\n host: localhost\n port: 5432").unwrap();
let root = doc.root().unwrap();
// Zero-copy: returns &str pointing into document memory
let host = root.at_path("/database/host").unwrap();
assert_eq!(host.scalar_str().unwrap(), "localhost");
// Navigation by path
let port = root.at_path("/database/port").unwrap();
assert_eq!(port.scalar_str().unwrap(), "5432");
ValueRef wraps NodeRef and provides typed accessors that interpret
YAML scalars on demand without allocation:
use fyaml::Document;
let doc = Document::parse_str("name: Alice\nage: 30\nactive: yes").unwrap();
let root = doc.root_value().unwrap();
// Zero-copy typed access
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)); // yes -> true
Non-plain scalars (quoted, literal |, folded >) are NOT
type-interpreted:
use fyaml::Document;
let doc = Document::parse_str("quoted: 'true'\nunquoted: true").unwrap();
let root = doc.root_value().unwrap();
// Quoted: string, not bool
assert_eq!(root.get("quoted").unwrap().as_bool(), None);
assert_eq!(root.get("quoted").unwrap().as_str(), Some("true"));
// Unquoted: interpreted as bool
assert_eq!(root.get("unquoted").unwrap().as_bool(), Some(true));
Use Document::edit() to get an exclusive Editor for modifications:
use fyaml::Document;
let mut doc = Document::parse_str("name: Alice").unwrap();
// Mutation phase - NodeRef cannot exist during this
{
let mut ed = doc.edit();
ed.set_yaml_at("/name", "'Bob'").unwrap(); // Preserve quotes
ed.set_yaml_at("/age", "30").unwrap(); // Add new key
ed.delete_at("/name").unwrap(); // Delete key
ed.set_yaml_at("/name", "\"Charlie\"").unwrap(); // Re-add
}
// Read phase - safe to access nodes again
let root = doc.root().unwrap();
assert_eq!(root.at_path("/name").unwrap().scalar_str().unwrap(), "Charlie");
assert_eq!(root.at_path("/age").unwrap().scalar_str().unwrap(), "30");
Building complex structures:
use fyaml::Document;
let mut doc = Document::new().unwrap();
{
let mut ed = doc.edit();
let root = ed.build_from_yaml("users:\n - name: Alice\n - name: Bob").unwrap();
ed.set_root(root).unwrap();
}
assert!(doc.root().is_some());
Modifying sequence elements:
use fyaml::Document;
let mut doc = Document::parse_str("items:\n - a\n - b\n - c").unwrap();
{
let mut ed = doc.edit();
// Replace by positive index
ed.set_yaml_at("/items/0", "first").unwrap();
// Replace by negative index (Python-style: -1 = last element)
ed.set_yaml_at("/items/-1", "last").unwrap();
}
assert_eq!(doc.at_path("/items/0").unwrap().scalar_str().unwrap(), "first");
assert_eq!(doc.at_path("/items/1").unwrap().scalar_str().unwrap(), "b");
assert_eq!(doc.at_path("/items/2").unwrap().scalar_str().unwrap(), "last");
Use FyParser for parsing YAML streams with multiple documents:
use fyaml::FyParser;
let yaml = "---\ndoc1: value1\n---\ndoc2: value2";
let parser = FyParser::from_string(yaml).unwrap();
let docs: Vec<_> = parser.doc_iter().filter_map(|r| r.ok()).collect();
assert_eq!(docs.len(), 2);
// Each document is independent
assert_eq!(docs[0].at_path("/doc1").unwrap().scalar_str().unwrap(), "value1");
assert_eq!(docs[1].at_path("/doc2").unwrap().scalar_str().unwrap(), "value2");
For CLI tools that read YAML from stdin:
use fyaml::Document;
// Single document from stdin
let doc = Document::from_stdin().unwrap();
println!("{}", doc.emit().unwrap());
For multi-document streams:
use fyaml::FyParser;
// Default: line-buffered mode for interactive/streaming use
let parser = FyParser::from_stdin().unwrap();
for doc_result in parser.doc_iter() {
let doc = doc_result.unwrap();
println!("{}", doc.emit().unwrap());
}
For batch processing where efficiency matters more than interactivity:
use fyaml::FyParser;
// Block-buffered mode: more efficient for large inputs
let parser = FyParser::from_stdin_with_line_buffer(false).unwrap();
for doc_result in parser.doc_iter() {
// Process each document
}
Value works with any serde-compatible format:
use fyaml::Value;
let value: Value = "key: value".parse().unwrap();
// Convert to JSON
let json = serde_json::to_string(&value).unwrap();
assert_eq!(json, r#"{"key":"value"}"#);
// Parse from JSON
let from_json: Value = serde_json::from_str(&json).unwrap();
use fyaml::Document;
let doc = Document::parse_str("a: 1\nb: 2\nc: 3").unwrap();
let root = doc.root().unwrap();
for (key, value) in root.map_iter() {
println!("{}: {}", key.scalar_str().unwrap(), value.scalar_str().unwrap());
}
use fyaml::Document;
let doc = Document::parse_str("- apple\n- banana\n- cherry").unwrap();
let root = doc.root().unwrap();
for item in root.seq_iter() {
println!("{}", item.scalar_str().unwrap());
}
use fyaml::{Document, NodeType};
let doc = Document::parse_str("key: value").unwrap();
let root = doc.root().unwrap();
assert!(root.is_mapping());
assert_eq!(root.kind(), NodeType::Mapping);
let value = root.at_path("/key").unwrap();
assert!(value.is_scalar());
| Type | Description |
|---|---|
Document |
Parsed YAML document (owns the data) |
NodeRef<'doc> |
Zero-copy reference to a node (borrows document) |
ValueRef<'doc> |
Zero-copy typed access (wraps NodeRef) |
Editor<'doc> |
Exclusive mutable access to document |
FyParser |
Multi-document stream parser |
Value |
Owned serde-compatible YAML value |
Number |
Numeric value: Int(i64), UInt(u64),
Float(f64) |
TaggedValue |
Value with an associated YAML tag |
ParseError |
Rich parse error with line/column location |
| Type | Variants |
|---|---|
NodeType |
Scalar, Sequence,
Mapping |
NodeStyle |
Plain, SingleQuoted,
DoubleQuoted, Literal, Folded,
etc. |
| Method | Description |
|---|---|
Document::parse_str(yaml) |
Parse YAML string into Document |
Document::new() |
Create empty document |
Document::from_stdin() |
Parse single document from stdin |
doc.root() |
Get root node as Option<NodeRef> |
doc.root_value() |
Get root node as Option<ValueRef> |
doc.at_path(path) |
Navigate to node by path |
doc.edit() |
Get exclusive Editor for mutations |
doc.emit() |
Emit document as YAML string |
| Method | Description |
|---|---|
node.kind() |
Get node type (NodeType) |
node.style() |
Get node style (NodeStyle) |
node.is_scalar() |
Check if node is a scalar |
node.is_mapping() |
Check if node is a mapping |
node.is_sequence() |
Check if node is a sequence |
node.is_quoted() |
Check if scalar is quoted |
node.is_non_plain() |
Check if scalar has non-plain style |
node.scalar_str() |
Get scalar as &str (zero-copy) |
node.scalar_bytes() |
Get scalar as &[u8] (zero-copy) |
node.at_path(path) |
Navigate to child by path |
node.seq_iter() |
Iterate over sequence items |
node.map_iter() |
Iterate over mapping key-value pairs |
node.seq_len() |
Get sequence length |
node.map_len() |
Get mapping length |
node.seq_get(i) |
Get sequence item by index |
node.map_get(key) |
Get mapping value by string key |
node.tag_str() |
Get YAML tag (zero-copy) |
node.emit() |
Emit node as YAML string |
| Method | Description |
|---|---|
value.as_str() |
Get string (zero-copy) Option<&str> |
value.as_bool() |
Interpret as boolean (yes/no/on/off/true/false) |
value.as_i64() |
Interpret as signed integer (hex/octal/binary) |
value.as_u64() |
Interpret as unsigned integer |
value.as_f64() |
Interpret as float (.inf, .nan support) |
value.is_null() |
Check for null/~/empty |
value.is_scalar() |
Check if scalar |
value.is_sequence() |
Check if sequence |
value.is_mapping() |
Check if mapping |
value.get(key) |
Get mapping value by key |
value.index(i) |
Get sequence item by index |
value.at_path(path) |
Navigate by path |
value.seq_iter() |
Iterate over sequence as ValueRef |
value.map_iter() |
Iterate over mapping as (ValueRef, ValueRef) |
value.tag() |
Get YAML tag (zero-copy) |
| Method | Description |
|---|---|
ed.set_yaml_at(path, yaml) |
Set/replace value at path (mappings and sequences) |
ed.delete_at(path) |
Delete value at path, returns bool |
ed.build_from_yaml(yaml) |
Build detached node from YAML |
ed.build_scalar(value) |
Build plain scalar node |
ed.build_sequence() |
Build empty sequence node |
ed.build_mapping() |
Build empty mapping node |
ed.set_root(handle) |
Set document root |
ed.copy_node(node) |
Copy node from any document |
ed.seq_append_at(path, item) |
Append item to sequence at path |
ed.root() |
Read root during edit session |
ed.at_path(path) |
Navigate during edit session |
| Method | Description |
|---|---|
FyParser::from_string(yaml) |
Create parser from YAML string |
FyParser::from_stdin() |
Create parser from stdin (line-buffered) |
FyParser::from_stdin_with_line_buffer(b) |
Configurable buffering |
parser.doc_iter() |
Iterate over documents (yields
Result<Document>) |
| Method | Description |
|---|---|
parse() |
Parse YAML string into Value |
to_yaml_string() |
Emit as YAML string via libfyaml |
is_null() |
Check if value is null |
is_bool() |
Check if value is boolean |
is_number() |
Check if value is numeric |
is_string() |
Check if value is a string |
is_sequence() |
Check if value is a sequence |
is_mapping() |
Check if value is a mapping |
is_tagged() |
Check if value has a tag |
as_str() |
Get as &str if string |
as_i64() |
Get as i64 if numeric |
as_u64() |
Get as u64 if numeric |
as_f64() |
Get as f64 if numeric |
as_bool() |
Get as bool if boolean |
as_sequence() |
Get as &[Value] if sequence |
as_mapping() |
Get as &IndexMap if mapping |
as_tagged() |
Get as &TaggedValue if tagged |
get(key) |
Get value by key from mapping |
[key] / [idx] |
Index into mapping or sequence |
| Type | Yields | Description |
|---|---|---|
SeqIter<'doc> |
NodeRef<'doc> |
Sequence items |
MapIter<'doc> |
(NodeRef<'doc>, NodeRef<'doc>) |
Mapping key-value pairs |
DocumentIterator |
Result<Document> |
Documents in stream |
libc - C library bindingsfyaml-sys - FFI bindings to libfyamllog - Logging frameworkserde - Serialization frameworkindexmap - Order-preserving map for YAML mappings| Metric | Coverage |
|---|---|
| Lines | 88.44% |
| Functions | 91.29% |
| Regions | 90.44% |
| Library | Engine | Serde | Status |
|---|---|---|---|
| serde_yaml | unsafe-libyaml (libyaml transpiled to Rust) | Yes | Deprecated (2024-03) |
| serde_yml | unsafe-libyaml | Yes | Maintained (fork of serde_yaml) |
| serde-yaml-ng | unsafe-libyaml | Yes | Active (migrating to libyaml-safer) |
| saphyr | Pure Rust (fork of yaml-rust) | Soon | Active |
| yaml-rust2 | Pure Rust (fork of yaml-rust) | No | Active (high MSRV) |
| yaml-rust | Pure Rust | No | Unmaintained |
| fyaml | libfyaml (C library via FFI) | Yes | Development |
For serde integration: fyaml provides a serde-compatible Value
type with libfyaml-powered parsing and emission. Alternatives include
serde_yml or serde-yaml-ng (based on unsafe-libyaml).
For pure Rust: Use saphyr or yaml-rust2 (no C dependencies,
easier to audit).
For DOM manipulation and path queries: fyaml provides convenient
path-based navigation (/foo/0/bar) via libfyaml's YPATH support,
plus a Value type for programmatic manipulation.
For maximum performance on large files: fyaml benefits from
libfyaml's zero-copy architecture and streaming optimizations.
MIT License (c) 2024-2026 Valentin Lab. The LICENSE file is available with the source.
Add sequence element support to set_yaml_at [Valentin Lab]
Remove installation section from README.org [Valentin Lab]
The installation instructions are redundant since crates.io displays this information automatically and keeps it current with published versions.
Improve crates.io README rendering. [Valentin Lab]
Fixes serde_yaml/serde_yml displaying as subscript and changelog using reStructuredText headings instead of markdown.
Add comprehensive integration tests for coverage. [Valentin Lab]
tests/editor_edge_cases.rs for editor boundary conditionstests/emit_roundtrip.rs for YAML emit and reparsetests/error_coverage.rs for error type formattingtests/memory_safety.rs for large inputs and deep nestingtests/noderef_coverage.rs for NodeRef methodstests/parser_edge_cases.rs for multi-document parsingtests/scalar_parsing_edge_cases.rs for YAML scalar typestests/serde_coverage.rs for serde integrationtests/value_mutability.rs for Value mutationstests/valueref_coverage.rs for ValueRef accessorsREADME.org with coverage metrics table (88.44% lines)Improves test coverage from ~75% to 88%+ by exercising edge cases, error paths, and lesser-used API methods.
Add rich parse errors with line/column location info. [Valentin Lab]
ParseError type with line(), column(), location() accessorsdiag module to capture libfyaml errors via fy_diag callbacksFYPCF_QUIET on all parse configs to suppress stderr outputDocument::parse_str, from_string, from_bytes to return
Error::ParseError with location infoEditor::build_from_yaml with RAII DiagGuard for diag restorationFyParser stream iterator to capture errors with locationThis makes the library suitable for GUI applications and IDEs that need structured error information without stderr pollution.
Refactor to enforce zero-copy aspects wherever possible. [Valentin Lab]
Add from_stdin_with_line_buffer() for configurable stdin buffering. [Valentin Lab]
Allows callers to control whether stdin uses line-buffered or block-buffered mode. Line-buffered is useful for streaming/interactive use (process documents as lines arrive), while block-buffered is more efficient for batch processing.
The existing from_stdin() method now delegates to this with line_buffered=true
to preserve backward compatibility.
Add source links for libfyaml claims. [Valentin Lab]
Update license and documentation for release. [Valentin Lab]
Update LICENSE copyright year to 2024-2026
Clarify license text in README (explicitly state MIT)
Add generated/build files to .gitignore (/README.md, /.pkg, /Cargo.lock)
Use crates.io syntax in installation instructions. [Valentin Lab]
Suppress libfyaml stderr noise in empty document test. [Valentin Lab]