serde-libconfigasaurus 0.3.0

Serde serialization and deserialization for libconfig format
Documentation
//! Example demonstrating getting and setting all fundamental libconfig types
//! using the dynamic Config type.
//!
//! Libconfig has 8 value types:
//!   Scalars:  boolean, integer (i32), integer64 (i64), float (f64), string
//!   Aggregates: array (homogeneous scalars), list (heterogeneous values), group (named settings)

use libconfig::{Config, Value};

fn main() {
    // =========================================================================
    // GETTING values - parse a config that uses every libconfig type
    // =========================================================================

    let input = r#"
        # A top-level implicit group (no outer braces)
        name = "My Application";
        version = 1;
        version_long = 100000000000L;
        pi = 3.14159;
        enabled = true;

        # Integer literal formats: hex, binary, octal (all auto-promote to i64 if needed)
        hex_port = 0x1F90;   # = 8080 decimal
        bin_flag = 0b1010;   # = 10 decimal
        oct_val  = 0o17;     # = 15 decimal

        # An array: homogeneous scalar values in [ ]
        ports = [80, 443, 8080];

        # A list: heterogeneous values in ( ), can nest aggregates
        misc = ("text", 42, true, 3.14, [1, 2], ("nested", "list"));

        # A group: named settings in { }
        database = {
            host = "localhost";
            port = 5432;
            credentials = {
                username = "admin";
                password = "secret";
            };
        };
    "#;

    let mut cfg = Config::from_str(input).expect("Failed to parse config");

    // --- Boolean ---
    println!("=== Boolean ===");
    let enabled: bool = cfg["enabled"].as_bool().unwrap();
    println!("  get: enabled = {}", enabled);

    // --- Integer (i32) ---
    println!("\n=== Integer (i32) ===");
    let version: i32 = cfg["version"].as_i32().unwrap();
    println!("  get: version = {}", version);

    // --- Integer64 (i64) ---
    println!("\n=== Integer64 (i64) ===");
    let version_long: i64 = cfg["version_long"].as_i64().unwrap();
    println!("  get: version_long = {}", version_long);

    // --- Float (f64) ---
    println!("\n=== Float (f64) ===");
    let pi: f64 = cfg["pi"].as_f64().unwrap();
    println!("  get: pi = {}", pi);

    // --- String ---
    println!("\n=== String ===");
    let name: &str = cfg["name"].as_str().unwrap();
    println!("  get: name = {}", name);

    // --- Array (homogeneous scalars in [...]) ---
    println!("\n=== Array ===");
    let ports = cfg["ports"].as_array().unwrap();
    println!("  get: ports = {:?}", ports);
    println!("  get: ports[0] = {}", cfg["ports.0"].as_i32().unwrap());

    // --- List (heterogeneous values in (...)) ---
    println!("\n=== List ===");
    let misc = cfg["misc"].as_array().unwrap(); // as_array works for both Array and List
    println!("  get: misc has {} elements", misc.len());
    println!("  get: misc[0] = {}", misc[0].as_str().unwrap());
    println!("  get: misc[1] = {}", misc[1].as_i32().unwrap());
    println!("  get: misc[2] = {}", misc[2].as_bool().unwrap());
    println!("  get: misc[3] = {}", misc[3].as_f64().unwrap());
    println!("  get: misc[4] is array = {}", misc[4].is_array());
    println!("  get: misc[5] is list  = {}", misc[5].is_list());

    // --- Group (named settings in {...}) ---
    println!("\n=== Group ===");
    let db_group = cfg["database"].as_group().unwrap();
    println!(
        "  get: database has {} keys: {:?}",
        db_group.len(),
        db_group.keys().collect::<Vec<_>>()
    );
    println!(
        "  get: database.host = {}",
        cfg["database.host"].as_str().unwrap()
    );
    println!(
        "  get: database.port = {}",
        cfg["database.port"].as_i32().unwrap()
    );

    // --- Integer literal formats ---
    println!("\n=== Integer Literal Formats ===");
    println!("  hex 0x1F90 = {}", cfg["hex_port"].as_i32().unwrap()); // 8080
    println!("  bin 0b1010 = {}", cfg["bin_flag"].as_i32().unwrap()); // 10
    println!("  oct 0o17   = {}", cfg["oct_val"].as_i32().unwrap());  // 15

    // --- Dotted path lookup (including array indices) ---
    println!("\n=== Dotted Path Lookup ===");
    println!(
        "  lookup: database.credentials.username = {}",
        cfg.lookup("database.credentials.username")
            .unwrap()
            .as_str()
            .unwrap()
    );
    println!(
        "  lookup: ports.2 = {}",
        cfg.lookup("ports.2").unwrap().as_i32().unwrap()
    );
    println!(
        "  lookup: misc.5.0 = {}",
        cfg.lookup("misc.5.0").unwrap().as_str().unwrap()
    );
    println!("  lookup: nonexistent = {:?}", cfg.lookup("nonexistent"));

    // =========================================================================
    // SETTING values - mutate in place using set() and lookup_mut()
    // =========================================================================
    println!("\n\n=== Setting Values ===");

    // --- Set a boolean ---
    *cfg.lookup_mut("enabled").unwrap() = Value::Bool(false);
    println!("  set: enabled = {}", cfg["enabled"]);

    // --- Set an integer ---
    *cfg.lookup_mut("version").unwrap() = Value::Integer(2);
    println!("  set: version = {}", cfg["version"]);

    // --- Set an integer64 ---
    *cfg.lookup_mut("version_long").unwrap() = Value::Integer64(200000000000);
    println!("  set: version_long = {}", cfg["version_long"]);

    // --- Set a float ---
    *cfg.lookup_mut("pi").unwrap() = Value::Float(3.14);
    println!("  set: pi = {}", cfg["pi"]);

    // --- Set a string ---
    *cfg.lookup_mut("name").unwrap() = Value::String("Updated App".to_string());
    println!("  set: name = {}", cfg["name"]);

    // --- Set an array element ---
    *cfg.lookup_mut("ports.0").unwrap() = Value::Integer(9090);
    println!("  set: ports[0] = {}", cfg["ports.0"]);

    // --- Replace an entire array ---
    cfg.set("ports", Value::Array(vec![Value::Integer(8080), Value::Integer(8443)]));
    println!("  set: ports = {:?}", cfg["ports"].as_array().unwrap());

    // --- Set a list element ---
    *cfg.lookup_mut("misc.0").unwrap() = Value::String("updated".to_string());
    println!("  set: misc[0] = {}", cfg["misc.0"]);

    // --- Replace an entire list ---
    cfg.set("misc", Value::List(vec![
        Value::String("mixed".to_string()),
        Value::Integer(99),
        Value::Bool(false),
    ]));
    println!("  set: misc = {}", cfg["misc"]);

    // --- Set a nested group field ---
    *cfg.lookup_mut("database.host").unwrap() = Value::String("10.0.0.1".to_string());
    println!("  set: database.host = {}", cfg["database.host"]);

    // --- Set a deeply nested field ---
    *cfg.lookup_mut("database.credentials.password").unwrap() =
        Value::String("new_secret".to_string());
    println!(
        "  set: database.credentials.password = {}",
        cfg["database.credentials.password"]
    );

    // --- Auto-create intermediate groups with set() ---
    cfg.set("cache.redis.host", Value::String("127.0.0.1".to_string()));
    cfg.set("cache.redis.port", Value::Integer(6379));
    println!("  set: cache.redis.host = {}", cfg["cache.redis.host"]);
    println!("  set: cache.redis.port = {}", cfg["cache.redis.port"]);

    // =========================================================================
    // REMOVING values
    // =========================================================================
    println!("\n\n=== Removing Values ===");

    // --- Remove a top-level key (returns the removed value) ---
    let removed = cfg.remove("enabled");
    println!("  remove: 'enabled' returned {:?}", removed);
    println!("  remove: lookup 'enabled' after = {:?}", cfg.lookup("enabled"));

    // --- Remove a nested key ---
    cfg.remove("database.credentials.password");
    println!("  remove: 'database.credentials.password' gone = {}", cfg.lookup("database.credentials.password").is_none());

    // --- Remove an array element (subsequent elements shift down) ---
    // ports is currently [8080, 8443]; removing [0] leaves [8443]
    cfg.remove("ports.[0]");
    println!("  remove: ports after removing [0] = {:?}", cfg["ports"].as_array().unwrap());

    // =========================================================================
    // Building a Config from scratch
    // =========================================================================
    println!("\n\n=== Building a Config from Scratch ===");

    let mut built = Config::new();
    built.set("title", Value::String("New Config".to_string()));
    built.set("debug", Value::Bool(true));
    built.set("max_retries", Value::Integer(3));
    built.set("timeout_ms", Value::Integer64(30000));
    built.set("ratio", Value::Float(0.75));
    built.set("tags", Value::Array(vec![
        Value::String("prod".to_string()),
        Value::String("us-east".to_string()),
    ]));
    built.set("metadata", Value::List(vec![
        Value::String("info".to_string()),
        Value::Integer(42),
        Value::Bool(true),
    ]));
    built.set("options.key", Value::String("value".to_string()));
    println!("{}", built.to_string().unwrap());

    // =========================================================================
    // Serialize the modified config back to libconfig format
    // =========================================================================
    println!("\n\n=== Final Serialized Output ===");
    let output = cfg.to_string().expect("Failed to serialize");
    println!("{}", output);
}