Skip to main content

Module value_api

Module value_api 

Source
Expand description

Dynamically-typed value type for libconfig data.

arachnid logo

§Value API Documentation

The Value type provides a dynamically-typed representation of libconfig data.

§Overview

When you don’t know the structure of your configuration at compile time, you can deserialize into a Value and traverse it dynamically.

§Value Variants

pub enum Value {
    Bool(bool),              // Boolean value
    Integer(i32),            // 32-bit integer
    Integer64(i64),          // 64-bit integer
    Float(f64),              // Floating point number
    String(String),          // String value
    Array(Vec<Value>),       // Array (homogeneous sequence)
    List(Vec<Value>),        // List (heterogeneous sequence)
    Group(Map),              // Group (object/map)
}

pub type Map = IndexMap<String, Value>;

§Usage Examples

§Basic Access

use libconfig::Value;

let config = r#"
    {
        name = "My App";
        version = 1;
        enabled = true;
    }
"#;

let v = Value::from_str(config).unwrap();

// Index with square brackets
println!("{}", v["name"]);     // "My App"
println!("{}", v["version"]);  // 1
println!("{}", v["enabled"]);  // true

§Nested Access

let config = r#"
    {
        database = {
            host = "localhost";
            port = 5432;
        };
    }
"#;

let v = Value::from_str(config).unwrap();

// Access nested values
println!("{}", v["database"]["host"]);  // "localhost"
println!("{}", v["database"]["port"]);  // 5432

§Array Access

let config = r#"
    {
        ports = [8080, 8081, 8082];
    }
"#;

let v = Value::from_str(config).unwrap();

// Index into arrays
println!("{}", v["ports"][0]);  // 8080
println!("{}", v["ports"][1]);  // 8081

§Path Lookup

let config = r#"
    {
        application = {
            window = {
                title = "My App";
            };
        };
    }
"#;

let v = Value::from_str(config).unwrap();

// Use dotted path
if let Some(title) = v.lookup("application.window.title") {
    println!("Title: {}", title);  // "My App"
}

§Type Checking Methods

let v = Value::Integer(42);

// Check type
assert!(v.is_integer());      // true
assert!(v.is_i32());          // true
assert!(v.is_number());       // true
assert!(!v.is_string());      // false

// Other type checks:
// - is_bool()
// - is_i64()
// - is_float()
// - is_string()
// - is_array()
// - is_list()
// - is_group()

§Type Conversion Methods

let v = Value::Integer(42);

// Convert to native types
assert_eq!(v.as_i32(), Some(42));
assert_eq!(v.as_i64(), Some(42));
assert_eq!(v.as_f64(), Some(42.0));

let v = Value::String("hello".to_string());
assert_eq!(v.as_str(), Some("hello"));

let v = Value::Array(vec![Value::Integer(1), Value::Integer(2)]);
assert_eq!(v.as_array().unwrap().len(), 2);

// Conversion methods:
// - as_bool() -> Option<bool>
// - as_i32() -> Option<i32>
// - as_i64() -> Option<i64>
// - as_f64() -> Option<f64>
// - as_str() -> Option<&str>
// - as_array() -> Option<&Vec<Value>>
// - as_group() -> Option<&Map>

§Access Methods

// Get by key (for groups)
if let Some(value) = v.get("database") {
    println!("Found database config");
}

// Get by index (for arrays/lists)
if let Some(value) = v.get_index(0) {
    println!("First element: {}", value);
}

// Get mutable reference
if let Some(value) = v.get_mut("name") {
    *value = Value::String("New Name".to_string());
}

§Iterating

§Over Group Fields

let config = r#"
    {
        features = {
            auth = true;
            cache = false;
        };
    }
"#;

let v = Value::from_str(config).unwrap();

if let Some(features) = v["features"].as_group() {
    for (key, value) in features {
        println!("{}: {}", key, value);
    }
}

§Over Array Elements

let config = r#"
    {
        servers = ["web-1", "web-2", "api-1"];
    }
"#;

let v = Value::from_str(config).unwrap();

if let Some(servers) = v["servers"].as_array() {
    for server in servers {
        println!("Server: {}", server);
    }
}

§Type Comparisons

You can directly compare Value with native Rust types:

let v = Value::Integer(42);
assert!(v == 42);

let v = Value::String("hello".to_string());
assert!(v == "hello");

let v = Value::Bool(true);
assert!(v == true);

§Serialization

Value implements Serialize, so you can convert it back to libconfig format:

use libconfig::Value;

let config = r#"{ name = "Test"; }"#;
let v = Value::from_str(config).unwrap();

// Serialize back to libconfig
let output = v.to_string().unwrap();
println!("{}", output);

§Converting Between Value and Typed Structs

You can convert Value to a concrete type:

use serde::Deserialize;
use libconfig::{from_value, Value};

#[derive(Deserialize)]
struct DatabaseConfig {
    host: String,
    port: i32,
}

let config = r#"
    {
        database = {
            host = "localhost";
            port = 5432;
        };
    }
"#;

let v = Value::from_str(config).unwrap();

// Extract and convert the database config
let db_config: DatabaseConfig = from_value(v["database"].clone()).unwrap();

println!("Host: {}", db_config.host);
println!("Port: {}", db_config.port);

§Error Handling

use libconfig::Value;

let config = r#"{ invalid syntax"#;

match Value::from_str(config) {
    Ok(v) => println!("Success: {:?}", v),
    Err(e) => eprintln!("Parse error: {}", e),
}

§Best Practices

  1. Use typed deserialization when possible - It’s faster and type-safe
  2. Use Value for unknown structures - When config comes from external sources
  3. Check types before conversion - Use is_*() methods before as_*() conversions
  4. Use lookup() for paths - More convenient than chaining get() calls
  5. Handle Options - All accessor methods return Option types

§Performance Notes

  • IndexMap is used for groups (preserves insertion order, O(1) lookups)
  • Index access is O(1) for arrays
  • Path lookup is O(n) where n is path depth
  • All strings are owned (no zero-copy yet)