Recursive AI

Parse YAML into Rust structs

All oneyaml Rust recipes

Read a YAML file into typed Rust data with serde: nested structs, a map, an optional section, a default and an enum. oneyaml is a serde data format like serde_json, so the same #[serde(...)] attributes apply.

Terminal
cargo add [email protected] serde -F serde/derive

The file:

YAML
# service.yaml
name: web
image: nginx:1.27
env:
  LOG_LEVEL: info
  PORT: "8080"
healthcheck:
  path: /healthz
  intervalSeconds: 10
restart: on-failure

The program:

RUST
use serde::Deserialize;
use std::collections::BTreeMap;
 
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Service {
    name: String,
    image: String,
    #[serde(default = "one")]
    replica_count: u32,
    env: BTreeMap<String, String>,
    healthcheck: Option<Healthcheck>,
    restart: Restart,
}
 
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Healthcheck {
    path: String,
    interval_seconds: u32,
}
 
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
enum Restart {
    Always,
    OnFailure,
    Never,
}
 
fn one() -> u32 {
    1
}
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let src = std::fs::read_to_string("service.yaml")?;
    let svc: Service = oneyaml::from_str(&src)?;
 
    println!("{} runs {} x{}", svc.name, svc.image, svc.replica_count);
    println!("env: {:?}", svc.env);
    if let Some(h) = &svc.healthcheck {
        println!("health check: {} every {}s", h.path, h.interval_seconds);
    }
    match svc.restart {
        Restart::Always => println!("restart: always"),
        Restart::OnFailure => println!("restart: on failure"),
        Restart::Never => println!("restart: never"),
    }
    Ok(())
}
Text
web runs nginx:1.27 x1
env: {"LOG_LEVEL": "info", "PORT": "8080"}
health check: /healthz every 10s
restart: on failure

Good to know

  • replicaCount isn’t in the file, so #[serde(default = "one")] fills it. Without a default, a missing field is an error that names it.
  • PORT: "8080" is quoted, so it reads as a string into the BTreeMap<String, String>. Unquoted, 8080 is an integer, and reading it into a String is a type error. oneyaml doesn’t convert scalars behind your back.
  • The YAML 1.2 Core schema is the default: plain yes, no, on and off are strings, and 0755 is 755. To read a file written for YAML 1.1, see YAML 1.1 booleans.
  • from_slice and from_reader read bytes and any io::Read. Each has a _with form that takes Settings.

When the shape isn’t known ahead of time, read a Value instead: read YAML without a schema. A field of the wrong type is reported with its line, column and path: errors with line and column.