Recursive AI

Anchors, aliases and merge keys

All oneyaml Rust recipes

Anchors (&name) and aliases (*name) reuse a node. Merge keys (<<) copy a mapping’s entries into another, as docker-compose and many CI files do. YAML 1.2 has no merge keys, so oneyaml reads << as a plain key unless you turn them on.

RUST
use oneyaml::{Document, Settings, Value};
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = "\
defaults: &defaults
  image: nginx:1.27
  replicas: 2
web:
  <<: *defaults
  replicas: 3
worker: *defaults
";
    // Aliases are expanded on read; `<<` is a plain key in YAML 1.2.
    let v: Value = oneyaml::from_str(src)?;
    println!("worker.image = {:?}", v["worker"]["image"].as_str());
    println!("web.image without merge keys = {:?}", v["web"]["image"].as_str());
 
    // Turn on YAML 1.1 merge keys for docker-compose-style files.
    let v: Value = oneyaml::from_str_with(src, &Settings::new().merge_keys(true))?;
    println!("web.image = {:?}, web.replicas = {:?}", v["web"]["image"].as_str(), v["web"]["replicas"].as_u64());
 
    // A Document keeps anchors and aliases as written, and can follow them.
    let doc = Document::parse(src)?;
    let worker = doc.get("worker").ok_or("no worker")?;
    println!("worker is an alias of {:?}", worker.alias_name());
    let target = worker.alias_target().ok_or("no target")?;
    println!("target anchor {:?}, image {:?}", target.anchor(), target.get("image").and_then(|n| n.as_str()));
    Ok(())
}
Text
worker.image = Some("nginx:1.27")
web.image without merge keys = None
web.image = Some("nginx:1.27"), web.replicas = Some(3)
worker is an alias of Some("defaults")
target anchor Some("defaults"), image Some("nginx:1.27")

Good to know

  • Settings::new().merge_keys(true) turns on merge keys alone. Compat::yaml_1_1() turns on every YAML 1.1 behavior, merge keys included.
  • Keys in the mapping win over merged ones: web.replicas is 3, not 2.
  • Alias expansion is budgeted. Expanding aliases, merges included, counts toward the alias budget of Limits, so a “billion laughs” file fails fast (read untrusted YAML safely).
  • A Document never expands aliases. It keeps & and * as written, and alias_target() follows one. An edit below an alias is refused, because it would change every place that shares the node.
  • NodeMut::push_alias, insert_alias and insert_merge write new aliases and merge keys.

Merge keys across Go, Rust and C# libraries, measured: YAML anchors, aliases and merge keys explained.