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>    (also converts in-range Integer64, per §2.6)
// - as_i64() -> Option<i64>
// - as_f64() -> Option<f64>    (also converts integers)
// - as_str() -> Option<&str>
// - as_array() -> Option<&Vec<Value>>   (arrays AND lists)
// - as_group() -> Option<&Map>
// Mutable: as_array_mut(), as_group_mut(), get_mut(), get_index_mut()

§Building Values

Native types convert directly, which keeps set() calls terse:

use libconfig::{Map, Value};

assert_eq!(Value::from(42), Value::Integer(42));
assert_eq!(Value::from("x"), Value::String("x".into()));
assert_eq!(Value::from(2.5), Value::Float(2.5));

let mut v = Value::Group(Map::new());
v.set("server.port", 8080).unwrap();          // impl Into<Value>
v.set("server.host", "0.0.0.0").unwrap();     // groups auto-created
assert_eq!(v["server.port"], 8080);

// set() validates names and returns typed errors
assert!(v.set("bad name", 1).is_err());

§Access Methods

use libconfig::Value;

let mut v = Value::from_str(r#"database = { host = "h"; }; ports = [80, 443];"#).unwrap();

// Get by key (groups) and by index (arrays/lists)
assert!(v.get("database").is_some());
assert_eq!(v["ports"].get_index(0).unwrap().as_i32(), Some(80));

// Mutate in place
*v.get_mut("database").unwrap().as_group_mut().unwrap()
    .get_mut("host").unwrap() = "elsewhere".into();
v["ports"].clone(); // reading via Index panics on missing paths; prefer lookup()
v.get_mut("ports").unwrap().as_array_mut().unwrap().push(8080.into());
assert_eq!(v["ports.[2]"], 8080);

§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, in either direction:

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

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

§Serialization

Value implements Serialize, so libconfig::to_string converts it back to libconfig text. Display (format!("{v}")) renders the compact value form instead (a group displays braced, as it would appear after name =):

use libconfig::Value;

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

// Serialize back to libconfig document text
let output = libconfig::to_string(&v).unwrap();
assert_eq!(output, "name = \"Test\";");

// Display renders the value form
assert_eq!(format!("{}", v), r#"{ name = "Test"; }"#);

§Converting Between Value and Typed Structs

from_value and to_value convert in both directions:

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

#[derive(Serialize, 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 part of a document
let db: DatabaseConfig = from_value(v["database"].clone()).unwrap();
assert_eq!(db.port, 5432);

// ... and back into a Value tree
let round = to_value(&db).unwrap();
assert_eq!(round, v["database"]);

§Error Handling

Parse errors carry the failure position and a typed kind:

use libconfig::{ErrorKind, Value};

let err = Value::from_str("ok = 1;\nbad = ;\n").unwrap_err();
eprintln!("{err}"); // e.g. "syntax error at line 2, column 7"
assert_eq!(err.line(), Some(2));
assert!(err.column().is_some());
assert!(!matches!(err.kind(), ErrorKind::Io(_)));

§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 - Index (v["a.b"]) panics on missing paths
  5. Handle results - Accessors return Option; set() returns Result

§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
  • Value strings are owned; typed deserialization of &str fields borrows from the input (zero-copy) when the string has no escapes or concatenation