Dynamically-typed value type for libconfig data.
<p align="center">
<img src="https://gitlab.com/rustographer/serde-libconfigasaurus/-/raw/v0.4.0/assets/v0.4/arachnid.png" alt="arachnid logo" width="300" >
</p>
# **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
```rust,ignore
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
```rust
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
```rust
# use libconfig::Value;
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
```rust
# use libconfig::Value;
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
```rust
# use libconfig::Value;
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
```rust
# use libconfig::Value;
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
```rust
# use libconfig::Value;
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:
```rust
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
```rust
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
```rust
# use libconfig::Value;
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
```rust
# use libconfig::Value;
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:
```rust
# use libconfig::Value;
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 =`):
```rust
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:
```rust
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:
```rust
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