fyaml
A safe Rust wrapper around the libfyaml C library for parsing and
manipulating YAML documents.
Overview
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.
Status
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.
Why libfyaml?
fyaml is built on libfyaml, a
modern C library that offers several advantages over the traditional
libyaml:
- Full YAML 1.2 compliance with YAML 1.3 preparation
- Zero-copy architecture for efficient large document handling
- No artificial limits (libyaml has a 1024-char implicit key limit)
- Up to 24x faster on large files in streaming mode (vs document mode)
- Rich manipulation APIs including YPATH expressions for path queries
- MIT licensed (as of v0.9.1)
This makes fyaml suitable for use cases requiring DOM manipulation,
YAML transformation tools, or configuration inspection utilities where
path-based queries are convenient.
Features
- Parse YAML strings into document objects
- Zero-copy scalar access via lifetime-bound
NodeRefandValueRef - Navigate nodes using path-based queries (e.g.,
/foo/bar,/list/0) - Support for all YAML node types: scalars, sequences, and mappings
- Iterate over mapping key-value pairs and sequence items
- Convert nodes back to YAML strings
- Multi-document stream parsing via
FyParser - Read YAML from stdin (single document or stream)
- Document mutation via
Editorwith compile-time safety - Style and comment preservation during edits: comments, quote styles, block/flow structure
ValueReftype: Zero-copy typed access with YAML type interpretationas_str(),as_bool(),as_i64(),as_f64(),is_null()- Non-plain scalars (quoted, literal, folded) preserved as strings
Valuetype: Pure Rust enum with serde support- Serialize/deserialize with any serde-compatible format (JSON, TOML, etc.)
- Emit YAML using libfyaml for standards-compliant output
- Convenient indexing:
value["key"],value[0]
Error Handling
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 Document;
let result = parse_str;
if let Err = result
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 available
All 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.
Zero-Copy Architecture
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 Document;
let doc = parse_str.unwrap;
let root = doc.root.unwrap;
let node = root.at_path.unwrap;
// Zero-copy: this &str points directly into the document's memory
let s: &str = node.scalar_str.unwrap;
assert_eq!;
// The reference is tied to the document's lifetime -
// this prevents use-after-free at compile time
This is particularly beneficial for:
- Large documents: Read gigabytes of YAML without doubling memory usage
- Config parsing: Extract only the values you need without copying everything
- High-throughput processing: Minimize allocations in hot paths
Style and Comment Preservation
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.
What IS preserved:
- Comments: Top-level, inline, and end-of-line comments
- Quote styles: Single-quoted (
'value'), double-quoted ("value"), and plain scalars - Block scalar styles: Literal (
|) and folded (>) blocks - Collection styles: Flow (
[a, b],{a: 1}) vs block (indented) sequences/mappings
use Document;
// Comments and quote styles are preserved through edits
let yaml = "# Database configuration
database:
host: 'localhost' # local dev server
port: 5432
";
let mut doc = parse_str.unwrap;
let output = doc.emit.unwrap;
// Comments preserved
assert!;
assert!;
// Quote style preserved
assert!;
Block scalars are also preserved:
use Document;
let yaml = "script: |
echo hello
echo world
name: test
";
let mut doc = parse_str.unwrap;
let output = doc.emit.unwrap;
// Literal block style (|) is preserved for the script
assert!;
Formatting notes:
- Flow collections (
[a, b],{a: 1}) are preserved as flow style but may be reformatted across multiple lines by libfyaml's emitter
Path Syntax
Paths use / as the separator (YPATH/JSON Pointer style):
/key- access a mapping key/0- access a sequence index/parent/child/0- nested access
use Document;
let yaml = "
database:
host: localhost
ports:
- 5432
- 5433
";
let doc = parse_str.unwrap;
let root = doc.root.unwrap;
// Access mapping key
let db = root.at_path.unwrap;
assert!;
// Nested access
let host = root.at_path.unwrap;
assert_eq!;
// Sequence index
let first_port = root.at_path.unwrap;
assert_eq!;
Usage
Working with Value (high-level, owned)
The Value type provides a convenient, serde-compatible way to work
with YAML:
use Value;
// Parse YAML
let value: Value = "name: Alice\nage: 30".parse.unwrap;
// Access values with indexing
assert_eq!;
assert_eq!;
// Emit back to YAML
let yaml = value.to_yaml_string.unwrap;
Zero-copy with Document and NodeRef
For more control and zero-copy scalar access, use the Document API:
use Document;
let doc = parse_str.unwrap;
let root = doc.root.unwrap;
// Zero-copy: returns &str pointing into document memory
let host = root.at_path.unwrap;
assert_eq!;
// Navigation by path
let port = root.at_path.unwrap;
assert_eq!;
Zero-copy typed access with ValueRef
ValueRef wraps NodeRef and provides typed accessors that interpret
YAML scalars on demand without allocation:
use Document;
let doc = parse_str.unwrap;
let root = doc.root_value.unwrap;
// Zero-copy typed access
assert_eq!;
assert_eq!;
assert_eq!; // yes -> true
Non-plain scalars (quoted, literal |, folded >) are NOT
type-interpreted:
use Document;
let doc = parse_str.unwrap;
let root = doc.root_value.unwrap;
// Quoted: string, not bool
assert_eq!;
assert_eq!;
// Unquoted: interpreted as bool
assert_eq!;
Mutation with Editor
Use Document::edit() to get an exclusive Editor for modifications:
use Document;
let mut doc = parse_str.unwrap;
// Mutation phase - NodeRef cannot exist during this
// Read phase - safe to access nodes again
let root = doc.root.unwrap;
assert_eq!;
assert_eq!;
Building complex structures:
use Document;
let mut doc = new.unwrap;
assert!;
Modifying sequence elements:
use Document;
let mut doc = parse_str.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
Multi-document parsing with FyParser
Use FyParser for parsing YAML streams with multiple documents:
use FyParser;
let yaml = "---\ndoc1: value1\n---\ndoc2: value2";
let parser = from_string.unwrap;
let docs: = parser.doc_iter.filter_map.collect;
assert_eq!;
// Each document is independent
assert_eq!;
assert_eq!;
Reading from stdin
For CLI tools that read YAML from stdin:
use Document;
// Single document from stdin
let doc = from_stdin.unwrap;
println!;
For multi-document streams:
use FyParser;
// Default: line-buffered mode for interactive/streaming use
let parser = from_stdin.unwrap;
for doc_result in parser.doc_iter
For batch processing where efficiency matters more than interactivity:
use FyParser;
// Block-buffered mode: more efficient for large inputs
let parser = from_stdin_with_line_buffer.unwrap;
for doc_result in parser.doc_iter
Serde integration
Value works with any serde-compatible format:
use Value;
let value: Value = "key: value".parse.unwrap;
// Convert to JSON
let json = to_string.unwrap;
assert_eq!;
// Parse from JSON
let from_json: Value = from_str.unwrap;
Iterating over mappings
use Document;
let doc = parse_str.unwrap;
let root = doc.root.unwrap;
for in root.map_iter
Iterating over sequences
use Document;
let doc = parse_str.unwrap;
let root = doc.root.unwrap;
for item in root.seq_iter
Checking node types
use ;
let doc = parse_str.unwrap;
let root = doc.root.unwrap;
assert!;
assert_eq!;
let value = root.at_path.unwrap;
assert!;
API Reference
Main Types
Enums
Document Methods
NodeRef Methods (zero-copy)
ValueRef Methods (zero-copy typed access)
Editor Methods
FyParser Methods
Value Methods
Iterators
Dependencies
libc- C library bindingsfyaml-sys- FFI bindings to libfyamllog- Logging frameworkserde- Serialization frameworkindexmap- Order-preserving map for YAML mappings
Test Coverage
Other Rust YAML Libraries
Choosing a Library
-
For serde integration:
fyamlprovides a serde-compatibleValuetype with libfyaml-powered parsing and emission. Alternatives includeserde_ymlorserde-yaml-ng(based on unsafe-libyaml). -
For pure Rust: Use
saphyroryaml-rust2(no C dependencies, easier to audit). -
For DOM manipulation and path queries:
fyamlprovides convenient path-based navigation (/foo/0/bar) via libfyaml's YPATH support, plus aValuetype for programmatic manipulation. -
For maximum performance on large files:
fyamlbenefits from libfyaml's zero-copy architecture and streaming optimizations.
License
MIT License (c) 2024-2026 Valentin Lab. The LICENSE file is available with the source.
Changelog
0.4.0 (2026-01-24)
New
-
Add sequence element support to
set_yaml_at[Valentin Lab]- Support positive and negative (Python-style) indices for sequences
- Validate index bounds and return error for out-of-bounds access
- Update documentation with sequence examples and supported parent types
- Add comprehensive tests for sequence manipulation edge cases
Changes
-
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.
Fix
-
Improve crates.io README rendering. [Valentin Lab]
- Add #+OPTIONS: ^:nil to prevent underscore subscript interpretation
- Add .gitchangelog.rc with markdown output format for proper headings
Fixes serde_yaml/serde_yml displaying as subscript and changelog using reStructuredText headings instead of markdown.
0.3.0 (2026-01-22)
New
-
Add comprehensive integration tests for coverage. [Valentin Lab]
- Add
tests/editor_edge_cases.rsfor editor boundary conditions - Add
tests/emit_roundtrip.rsfor YAML emit and reparse - Add
tests/error_coverage.rsfor error type formatting - Add
tests/memory_safety.rsfor large inputs and deep nesting - Add
tests/noderef_coverage.rsforNodeRefmethods - Add
tests/parser_edge_cases.rsfor multi-document parsing - Add
tests/scalar_parsing_edge_cases.rsfor YAML scalar types - Add
tests/serde_coverage.rsfor serde integration - Add
tests/value_mutability.rsforValuemutations - Add
tests/valueref_coverage.rsforValueRefaccessors - Update
README.orgwith 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
-
Add rich parse errors with line/column location info. [Valentin Lab]
- Add
ParseErrortype withline(),column(),location()accessors - Add
diagmodule to capture libfyaml errors viafy_diagcallbacks - Enable
FYPCF_QUIETon all parse configs to suppress stderr output - Update
Document::parse_str,from_string,from_bytesto returnError::ParseErrorwith location info - Update
Editor::build_from_yamlwith RAIIDiagGuardfor diag restoration - Update
FyParserstream iterator to capture errors with location
This makes the library suitable for GUI applications and IDEs that need structured error information without stderr pollution.
- Add
-
Refactor to enforce zero-copy aspects wherever possible. [Valentin Lab]
0.2.0 (2026-01-20)
New
-
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.
Changes
-
Add source links for libfyaml claims. [Valentin Lab]
- Link to libyaml source for 1024-char implicit key limit
- Link to libfyaml changelog for 24x performance claim
- Clarify streaming mode comparison context
-
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)
0.1.1 (2026-01-19)
Fix
- Use crates.io syntax in installation instructions. [Valentin Lab]
0.1.0 (2026-01-19)
Fix
- Suppress libfyaml stderr noise in empty document test. [Valentin Lab]