Skip to main content

Crate yaml_edit

Crate yaml_edit 

Source
Expand description

§yaml-edit

A Rust library for parsing and editing YAML files while preserving formatting, comments, and whitespace. Built with rowan for lossless syntax trees.

§Features

  • Lossless parsing - preserves all whitespace, comments, and original formatting
  • In-place editing - modify YAML structures while maintaining formatting
  • Error recovery - continues parsing even with syntax errors
  • Position tracking - detailed error locations for debugging

§Quick Start

use yaml_edit::Document;
use std::str::FromStr;

let doc = Document::from_str("name: old-project\nversion: 1.0.0")?;

if let Some(mapping) = doc.as_mapping() {
    mapping.set("name", "new-project");
    mapping.set("version", "2.0.0");
}

println!("{}", doc);  // Formatting preserved

§How It Works

This library uses a persistent syntax tree built on rowan. Understanding this model helps you use the library effectively:

§Lightweight Wrappers

Types like Mapping, Sequence, and Document are lightweight wrappers around syntax tree nodes:

let mapping = doc.as_mapping();  // Just a view into the tree
// mapping is cheap to clone, it's just a reference

§In-Place Mutations

Changes are applied directly to the underlying syntax tree using rowan’s splice_children operation:

let mapping = doc.as_mapping().unwrap();
mapping.set("key", "value");
// The change is visible through `doc` immediately
// No need to "put mapping back into doc"

§Shared Tree Structure

Multiple wrappers can reference the same underlying tree. When one is mutated, all see the change:

let mapping1 = doc.as_mapping().unwrap();
let mapping2 = doc.as_mapping().unwrap();

mapping2.set("new_key", "new_value");
// mapping1 also sees the change because they reference the same tree

This design enables ergonomic APIs without explicit ownership transfers, efficient mutations without copying, and preserved formatting because edits modify nodes in-place.

§Entry Points

§Document - Single-document YAML (most common)

use yaml_edit::Document;
use std::str::FromStr;

let doc = Document::from_file(&config_path)?;
// Or from a string
let doc = Document::from_str("key: value")?;

§YamlFile - Multi-document YAML

use yaml_edit::YamlFile;
use std::str::FromStr;

let yaml = YamlFile::from_str("---\ndoc1: value\n---\ndoc2: value")?;

for doc in yaml.documents() {
    // Process each document
}

§Mapping / Sequence - Working with collections

let doc = Document::from_str("key: value\nlist:\n  - item1\n  - item2")?;

if let Some(mapping) = doc.as_mapping() {
    mapping.set("new_key", "new_value");

    if let Some(list) = mapping.get_sequence("list") {
        list.push("item3");
    }
}

§Common Operations

§Editing mappings

let yaml = r#"
name: my-app
version: 1.0.0
author: Alice
"#;

let doc = Document::from_str(yaml)?;

if let Some(root) = doc.as_mapping() {
    root.set("version", "2.0.0");
    root.set("license", "MIT");
    root.remove("author");
    root.rename_key("name", "project_name");
}

§Working with sequences

let yaml = "items:\n  - one\n  - two\n";
let doc = Document::from_str(yaml)?;

if let Some(root) = doc.as_mapping() {
    if let Some(items) = root.get_sequence("items") {
        items.push("three");
        items.set(0, "first");  // Update by index
    }
}

§Nested modifications

let yaml = r#"
services:
  web:
    image: nginx:latest
    port: 8080
"#;

let doc = Document::from_str(yaml)?;

if let Some(root) = doc.as_mapping() {
    if let Some(services) = root.get_mapping("services") {
        if let Some(web) = services.get_mapping("web") {
            web.set("image", "nginx:alpine");
            web.set("port", 80);
        }
    }
}

§Path-based access

use yaml_edit::path::YamlPath;

// Get nested values
let host = doc.get_path("server.host");

// Set nested values (creates intermediate mappings)
doc.set_path("database.credentials.username", "admin");

// Array indexing
doc.get_path("servers[0].host");

§Visitor pattern

For traversing and analyzing documents:

use yaml_edit::{Document, visitor::{YamlVisitor, YamlAccept, ScalarCollector}};
use std::str::FromStr;

let doc = Document::from_str("name: my-app\nversion: 1.0.0")?;
let mut collector = ScalarCollector::new();
doc.accept(&mut collector);

// collector.scalars contains all scalar values

§Error Handling

use yaml_edit::{Document, YamlError};
use std::str::FromStr;

fn update_config(yaml: &str, new_version: &str) -> Result<String, YamlError> {
    let doc = Document::from_str(yaml)?;

    let root = doc.as_mapping()
        .ok_or_else(|| YamlError::InvalidOperation {
            operation: "get root mapping".to_string(),
            reason: "Document root is not a mapping".to_string(),
        })?;

    root.set("version", new_version);
    Ok(doc.to_string())
}

§Testing

# Run all tests
cargo test

# Run with all features
cargo test --all-features

# Run YAML Test Suite (requires submodule)
[git](git) submodule update --init
cargo test --test yaml_test_suite

§More Examples

See the examples/ directory for more detailed usage.

§License

See LICENSE file for details. A lossless YAML parser and editor.

This library provides a lossless parser for YAML files, preserving all whitespace, comments, and formatting. It is based on the rowan library.

§Mutability Model

Important: This library uses interior mutability through the rowan library. This means methods taking &self can still modify the underlying syntax tree.

§What This Means

  • Types like Mapping, Sequence, and Document can mutate even from &self
  • Changes are immediately visible to all holders of the syntax tree
  • You don’t need to mark variables as mut to call mutation methods

§Example

use yaml_edit::Document;
use std::str::FromStr;

let doc = Document::from_str("name: Alice").unwrap();  // Note: not `mut`
let mapping = doc.as_mapping().unwrap();  // Note: not `mut`

// Yet we can still mutate!
mapping.set("age", 30);  // This works despite `mapping` not being `mut`

assert_eq!(doc.to_string(), "name: Alice\nage: 30\n");

§Why This Design?

This design enables:

  • Efficient in-place mutations without cloning the entire tree
  • Sharing references while still allowing modifications
  • Lossless preservation of formatting and comments during edits

If you’re familiar with RefCell or Rc, this is similar - the tree uses internal synchronization to allow shared mutable access.

§Migration Note

If you’re coming from other YAML libraries, this might seem unusual. In most libraries, you need &mut to modify data. Here, you don’t. This is intentional and allows for a more flexible API while maintaining the guarantees of Rust’s borrow checker.

§Getting Started

§Parsing YAML

use yaml_edit::Document;
use std::str::FromStr;

let yaml = Document::from_str("name: Alice\nage: 30").unwrap();
let mapping = yaml.as_mapping().unwrap();

// Get values
let name = mapping.get("name").unwrap();
assert_eq!(name.as_scalar().unwrap().to_string(), "Alice");

§Modifying YAML

use yaml_edit::Document;
use std::str::FromStr;

let yaml = Document::from_str("name: Alice").unwrap();
let mapping = yaml.as_mapping().unwrap();

// Add a new field
mapping.set("age", 30);

// Update an existing field
mapping.set("name", "Bob");

// Remove a field
mapping.remove("age");

§Path-based Access

use yaml_edit::{Document, path::YamlPath};
use std::str::FromStr;

let yaml = Document::from_str("server:\n  host: localhost").unwrap();

// Get nested values
let host = yaml.get_path("server.host");
assert!(host.is_some());

// Set nested values (creates intermediate mappings)
yaml.set_path("server.port", 8080);
yaml.set_path("database.host", "db.example.com");

§Iterating Over Collections

use yaml_edit::Document;
use std::str::FromStr;

let yaml = Document::from_str("a: 1\nb: 2\nc: 3").unwrap();
let mapping = yaml.as_mapping().unwrap();

// Iterate over key-value pairs
for (key, value) in &mapping {
    println!("{:?}: {:?}", key, value);
}

// Use iterator methods
let count = (&mapping).into_iter().count();
assert_eq!(count, 3);

§Working with Sequences

use yaml_edit::Document;
use std::str::FromStr;

let yaml = Document::from_str("items:\n  - apple\n  - banana").unwrap();
let mapping = yaml.as_mapping().unwrap();
let sequence = mapping.get_sequence("items").unwrap();

// Iterate over items
for item in &sequence {
    println!("{:?}", item);
}

// Get specific item
let first = sequence.get(0);
assert!(first.is_some());

§Schema Validation

use yaml_edit::{Document, SchemaValidator};
use std::str::FromStr;

let yaml = Document::from_str("name: Alice\nage: 30").unwrap();

// Validate against JSON schema (no custom types)
let result = SchemaValidator::json().validate(&yaml);
assert!(result.is_ok());

§Position Tracking

use yaml_edit::Document;
use std::str::FromStr;

let text = "name: Alice\nage: 30";
let doc = Document::from_str(text).unwrap();

// Get line/column positions
let start = doc.start_position(text);
assert_eq!(start.line, 1);
assert_eq!(start.column, 1);

Re-exports§

pub use custom_tags::CompressedBinaryHandler;
pub use custom_tags::CustomTagError;
pub use custom_tags::CustomTagHandler;
pub use custom_tags::CustomTagParser;
pub use custom_tags::CustomTagRegistry;
pub use custom_tags::EnvVarHandler;
pub use custom_tags::JsonHandler;
pub use custom_tags::TimestampHandler;

Modules§

advanced
Advanced API for power users who need direct access to the underlying syntax tree.
anchor_resolution
Anchor and alias resolution for semantic YAML operations
custom_tags
Custom YAML tag system for format-preserving parsing and serialization
debug
Debug utilities for inspecting and understanding YAML structures.
error_recovery
Error recovery mechanisms for YAML parsing
path
Path-based access to YAML documents.
validator
YAML specification validator
visitor
Visitor pattern for traversing YAML AST nodes.

Structs§

Alias
A YAML alias reference (e.g., *anchor_name)
CustomSchema
Custom schema definition with user-defined validation rules
Directive
A YAML directive like %YAML 1.2
Document
A single YAML document
LineColumn
A line and column position in a YAML document (1-indexed).
Mapping
A YAML mapping (key-value pairs)
MappingBuilder
Builder for YAML mappings.
MappingEntry
A key-value pair in a YAML mapping
Parse
The result of a parse operation.
PositionedParseError
A positioned parse error containing location information.
Scalar
A YAML scalar value
ScalarValue
A scalar value with metadata about its style and content
SchemaValidator
Schema validator for YAML documents
Sequence
A YAML sequence (list)
SequenceBuilder
Builder for YAML sequences.
Set
A virtual AST node for YAML sets (!!set tagged scalars)
TaggedNode
A YAML tagged scalar (tag + value)
TextPosition
A text position in a YAML document, represented as byte offsets.
ValidationConfig
Configuration for whitespace and formatting validation
ValidationError
Error that occurs during schema validation
WhitespaceError
Whitespace and formatting validation errors
YamlBuilder
A builder for constructing YAML documents with a fluent API.
YamlFile
A YAML file containing one or more documents

Enums§

CustomValidationResult
Result of a custom validation function
Indentation
The indentation to use when writing a YAML file.
Lang
YAML language type for rowan.
ParseErrorKind
The kind of parse error, enabling structured matching without string parsing.
ScalarConversionError
Error type for scalar type conversions
ScalarStyle
Style of scalar representation in YAML
ScalarType
Type of a scalar value
Schema
YAML Schema types as defined in YAML 1.2 specification
SyntaxKind
YAML Concrete Syntax Tree (CST) node types.
ValidationErrorKind
Specific type of validation error that occurred
WhitespaceErrorCategory
Categories of whitespace errors
YamlError
Errors that can occur when working with YAML documents
YamlKind
The kind of YAML value.
YamlNode
A type-erased handle to a CST node returned from navigation methods.

Traits§

AsYaml
Trait for types that can be represented as YAML content.

Functions§

byte_offset_to_line_column
Convert a byte offset to line and column numbers in the given text.
lex
Tokenize YAML input with whitespace validation
lex_with_validation
Tokenize YAML input with whitespace and formatting validation
lex_with_validation_config
Tokenize YAML input with custom validation configuration
yaml_eq
Compare two AsYaml values for semantic equality.

Type Aliases§

ValidationResult
Result type for schema validation operations
YamlResult
Result type for yaml-edit operations