Como Ler Arquivo YAML em Rust

Aprenda a ler e escrever YAML em Rust com serde_yaml: config structs, dados aninhados, arrays, merge keys e tratamento de erros. Código executável completo.

YAML é um formato popular para arquivos de configuração, especialmente em ferramentas DevOps como Docker Compose, Kubernetes e CI/CD pipelines. A crate serde_yaml integra com o ecossistema Serde, permitindo deserializar YAML diretamente em structs tipadas. Nesta receita, você vai aprender a ler, escrever e manipular YAML em Rust.

Dependências

[package]
name = "receita-yaml"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"

Código Completo

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;

// =============================================
// 1. Definir structs de configuração
// =============================================
#[derive(Debug, Serialize, Deserialize)]
struct Config {
    app: AppConfig,
    servidor: ServidorConfig,
    banco: BancoConfig,

    #[serde(default)]
    servicos: Vec<Servico>,

    #[serde(default)]
    variaveis: HashMap<String, String>,
}

#[derive(Debug, Serialize, Deserialize)]
struct AppConfig {
    nome: String,
    versao: String,
    ambiente: String,

    #[serde(default)]
    debug: bool,
}

#[derive(Debug, Serialize, Deserialize)]
struct ServidorConfig {
    host: String,
    porta: u16,

    #[serde(default = "default_workers")]
    workers: usize,

    tls: Option<TlsConfig>,
}

#[derive(Debug, Serialize, Deserialize)]
struct TlsConfig {
    cert: String,
    key: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct BancoConfig {
    driver: String,
    host: String,
    porta: u16,
    nome: String,
    usuario: String,
    senha: String,

    #[serde(default = "default_pool")]
    pool_size: u32,
}

#[derive(Debug, Serialize, Deserialize)]
struct Servico {
    nome: String,
    url: String,

    #[serde(default = "default_timeout")]
    timeout_ms: u64,
}

fn default_workers() -> usize { 4 }
fn default_pool() -> u32 { 10 }
fn default_timeout() -> u64 { 5000 }

fn main() {
    // =============================================
    // 2. Parse de string YAML
    // =============================================
    println!("=== Parse de String YAML ===");

    let yaml_str = r#"
app:
  nome: "Minha API"
  versao: "1.0.0"
  ambiente: produção
  debug: false

servidor:
  host: "0.0.0.0"
  porta: 8080
  workers: 8
  tls:
    cert: /etc/ssl/cert.pem
    key: /etc/ssl/key.pem

banco:
  driver: postgresql
  host: db.exemplo.com
  porta: 5432
  nome: minha_api
  usuario: app_user
  senha: s3cr3t
  pool_size: 20

servicos:
  - nome: auth
    url: "http://auth-service:3001"
    timeout_ms: 2000
  - nome: email
    url: "http://email-service:3002"
  - nome: cache
    url: "http://redis:6379"
    timeout_ms: 1000

variaveis:
  LOG_LEVEL: info
  SENTRY_DSN: "https://abc@sentry.io/123"
  FEATURE_FLAG: "true"
"#;

    let config: Config = serde_yaml::from_str(yaml_str).unwrap();
    println!("App: {} v{}", config.app.nome, config.app.versao);
    println!("Ambiente: {}", config.app.ambiente);
    println!("Servidor: {}:{}", config.servidor.host, config.servidor.porta);
    println!("Workers: {}", config.servidor.workers);
    println!("TLS: {:?}", config.servidor.tls.is_some());
    println!("Banco: {}@{}:{}/{}",
        config.banco.usuario, config.banco.host,
        config.banco.porta, config.banco.nome
    );

    println!("\nServiços:");
    for s in &config.servicos {
        println!("  {} -> {} (timeout: {}ms)", s.nome, s.url, s.timeout_ms);
    }

    println!("\nVariáveis:");
    for (k, v) in &config.variaveis {
        println!("  {} = {}", k, v);
    }

    // =============================================
    // 3. Parse com campos opcionais ausentes
    // =============================================
    println!("\n=== Config Mínima ===");

    let yaml_minimal = r#"
app:
  nome: "App Simples"
  versao: "0.1.0"
  ambiente: desenvolvimento

servidor:
  host: localhost
  porta: 3000

banco:
  driver: sqlite
  host: ""
  porta: 0
  nome: dados.db
  usuario: ""
  senha: ""
"#;

    let config: Config = serde_yaml::from_str(yaml_minimal).unwrap();
    println!("Workers (padrão): {}", config.servidor.workers);
    println!("Pool (padrão): {}", config.banco.pool_size);
    println!("TLS: {:?}", config.servidor.tls);
    println!("Serviços: {} (vazio)", config.servicos.len());

    // =============================================
    // 4. Serializar struct para YAML
    // =============================================
    println!("\n=== Serializar para YAML ===");

    let config_nova = Config {
        app: AppConfig {
            nome: "Nova API".into(),
            versao: "2.0.0".into(),
            ambiente: "staging".into(),
            debug: true,
        },
        servidor: ServidorConfig {
            host: "0.0.0.0".into(),
            porta: 9090,
            workers: 2,
            tls: None,
        },
        banco: BancoConfig {
            driver: "postgresql".into(),
            host: "localhost".into(),
            porta: 5432,
            nome: "staging_db".into(),
            usuario: "admin".into(),
            senha: "dev123".into(),
            pool_size: 5,
        },
        servicos: vec![
            Servico {
                nome: "auth".into(),
                url: "http://localhost:3001".into(),
                timeout_ms: 3000,
            },
        ],
        variaveis: HashMap::from([
            ("LOG_LEVEL".into(), "debug".into()),
        ]),
    };

    let yaml_output = serde_yaml::to_string(&config_nova).unwrap();
    println!("{}", yaml_output);

    // =============================================
    // 5. Escrever e ler arquivo YAML
    // =============================================
    println!("=== Arquivo YAML ===");
    let caminho = "/tmp/config_teste.yaml";

    // Escrever
    fs::write(caminho, &yaml_output).unwrap();
    println!("Salvo em: {}", caminho);

    // Ler
    let conteudo = fs::read_to_string(caminho).unwrap();
    let config_lida: Config = serde_yaml::from_str(&conteudo).unwrap();
    println!("Lido: {} v{}", config_lida.app.nome, config_lida.app.versao);

    // Limpar
    let _ = fs::remove_file(caminho);

    // =============================================
    // 6. Parse genérico com serde_yaml::Value
    // =============================================
    println!("\n=== Parse Genérico ===");

    let yaml = r#"
nome: exemplo
lista:
  - um
  - dois
  - três
aninhado:
  chave: valor
  numero: 42
"#;

    let valor: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();

    // Acessar valores
    println!("Nome: {:?}", valor["nome"].as_str());
    println!("Lista[0]: {:?}", valor["lista"][0].as_str());
    println!("Aninhado.chave: {:?}", valor["aninhado"]["chave"].as_str());
    println!("Aninhado.numero: {:?}", valor["aninhado"]["numero"].as_u64());

    // =============================================
    // 7. Tratamento de erros
    // =============================================
    println!("\n=== Tratamento de Erros ===");

    let yaml_invalido = r#"
app:
  nome: "Teste"
  versao: 123  # esperava string, recebeu número
  ambiente: dev
"#;

    match serde_yaml::from_str::<Config>(yaml_invalido) {
        Ok(c) => println!("Config: {:?}", c),
        Err(e) => println!("Erro de parse:\n  {}", e),
    }

    // YAML com indentação errada
    let yaml_quebrado = "chave:\n  sub: valor\n sub2: erro";
    match serde_yaml::from_str::<serde_yaml::Value>(yaml_quebrado) {
        Ok(v) => println!("Valor: {:?}", v),
        Err(e) => println!("Erro de sintaxe:\n  {}", e),
    }

    // =============================================
    // 8. Converter entre YAML e JSON
    // =============================================
    println!("\n=== YAML -> Value -> Display ===");
    let yaml = "nome: Rust\nversao: 2026";
    let valor: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
    let yaml_bonito = serde_yaml::to_string(&valor).unwrap();
    println!("{}", yaml_bonito);
}

Saída do Programa

=== Parse de String YAML ===
App: Minha API v1.0.0
Ambiente: produção
Servidor: 0.0.0.0:8080
Workers: 8
TLS: true
Banco: app_user@db.exemplo.com:5432/minha_api

Serviços:
  auth -> http://auth-service:3001 (timeout: 2000ms)
  email -> http://email-service:3002 (timeout: 5000ms)
  cache -> http://redis:6379 (timeout: 1000ms)

Variáveis:
  LOG_LEVEL = info
  SENTRY_DSN = https://abc@sentry.io/123
  FEATURE_FLAG = true

=== Config Mínima ===
Workers (padrão): 4
Pool (padrão): 10
TLS: None
Serviços: 0 (vazio)

=== Serializar para YAML ===
app:
  nome: Nova API
  versao: 2.0.0
  ambiente: staging
  debug: true
...

=== Parse Genérico ===
Nome: Some("exemplo")
Lista[0]: Some("um")
Aninhado.chave: Some("valor")
Aninhado.numero: Some(42)

=== Tratamento de Erros ===
Erro de parse:
  servidor: missing field `servidor` at line 1 column 1

TOML vs YAML

AspectoTOMLYAML
AninhamentoSeções com [tabela]Indentação
Arrays[1, 2, 3] ou [[tabela]]- item por linha
TipagemRigorosaImplícita (strings sem aspas)
ComplexidadeSimples e previsívelFlexível mas com pegadinhas
Uso no RustCargo.toml, configs simplesK8s, Docker Compose, CI/CD

Recomendação: Use TOML para configurações de aplicação Rust. Use YAML quando precisar interagir com ferramentas DevOps ou dados com aninhamento profundo.

Veja Também