serde-libconfigasaurus 0.4.0

Serde serialization and deserialization for libconfig format
Documentation
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