<p align="center">
<img src="https://gitlab.com/rustographer/serde-libconfigasaurus/-/raw/v0.4.0/assets/v0.4/libconfigasaurus.png" alt="libconfigasaurus logo" width="300" >
</p>
# **serde-libconfigasaurus**
A Rust [serde](https://serde.rs/) implementation for the [libconfig](https://hyperrealm.github.io/libconfig/) configuration format.
## Supported Types
All libconfig value types are supported:
| boolean | `bool` / `Value::Bool` |
| integer | `i32` / `Value::Integer` |
| integer64 | `i64` / `Value::Integer64` |
| float | `f64` / `Value::Float` |
| string | `String` / `Value::String` |
| array `[...]` | `Vec<T>` / `Value::Array` (homogeneous scalars) |
| list `(...)` | tuples, `Vec` of aggregates / `Value::List` (heterogeneous values) |
| group `{...}` | structs / `Value::Group` (named settings) |
Integer literals support decimal, hex (`0xFF`), binary (`0b1010`), and octal (`0o17` / `0q17`) formats. Values exceeding the 32-bit range are automatically promoted to 64-bit, or use the `L` / `LL` suffix explicitly (`100L`).
Rust enums round-trip too: unit variants are written as strings (`"Variant"`), and data-carrying variants as single-setting groups (`{ Variant = value; }`).
## Quick Start
```toml
[dependencies]
serde-libconfigasaurus = "0.4"
serde = { version = "1.0", features = ["derive"] }
```
### Typed Deserialization
```rust
use serde::Deserialize;
use libconfig::from_str;
#[derive(Debug, Deserialize)]
struct Server {
hostname: String,
port: i32,
ssl: bool,
}
let config = r#"
hostname = "localhost";
port = 8080;
ssl = true;
"#;
let server: Server = from_str(config).unwrap();
```
### Serialization
```rust
use serde::Serialize;
use libconfig::to_string;
#[derive(Serialize)]
struct AppSettings {
name: String,
version: i32,
}
let settings = AppSettings {
name: "My App".to_string(),
version: 1,
};
// The top level of a libconfig document is an implicit setting list;
// braces only appear around nested groups.
let output = to_string(&settings).unwrap();
// name = "My App";
// version = 1;
```
### Dynamic Config
When you don't know the structure at compile time, use `Config` for dynamic access:
```rust
use libconfig::Config;
let cfg = Config::from_str(r#"
title = "My HTTP server";
listen_ports = [80, 443];
misc = {
owner = "Chuck Norris";
contact = {
phone = "415-256-9999";
emails = ["chuck@norris.com", "chuck.norris@gmail.com"];
};
};
"#).unwrap();
// Index with dotted paths
assert_eq!(cfg["title"].as_str(), Some("My HTTP server"));
assert_eq!(cfg["listen_ports.0"].as_i32(), Some(80));
assert_eq!(cfg["misc.contact.phone"].as_str(), Some("415-256-9999"));
```
### Reading from a File
```rust ,no_run
use libconfig::Config;
let cfg = Config::from_file("settings.cfg").unwrap();
println!("{}", cfg["database.host"]);
```
### More API
- `to_value` / `from_value` convert between any serde type and `Value`
- `Config::to_file` writes a document back to disk; `from_reader`/`to_writer`
work with any `Read`/`Write`
- Parse errors carry positions: `err.line()`, `err.column()`
- `Option` fields round-trip naturally: `None` fields are omitted on write,
and absent settings read back as `None`
- `set` accepts anything convertible into a `Value`: `cfg.set("port", 8080)`
- `WriteOptions` controls output style (float precision, scientific
notation, tab width, `:` vs `=`, brace placement, separators) — all
combinations emit grammar-valid text
- Custom `@include` resolvers via `Config::from_str_with_include_fn`
(wildcards, search paths — the `config_set_include_func` equivalent)
- `&str` struct fields deserialize zero-copy when the string needs no
escape decoding or concatenation
## Setting Paths
Values can be read, written, and removed using libconfig-style dotted paths. Array and list elements are addressed with bracket notation (`[index]`) or bare numeric segments:
```rust
use libconfig::{Config, Value};
let mut cfg = Config::from_str(r#"ports = [80, 443];"#).unwrap();
// Read
assert_eq!(cfg.lookup("ports.[0]").unwrap().as_i32(), Some(80));
assert_eq!(cfg.lookup("ports.0").unwrap().as_i32(), Some(80)); // bare numeric also works
// Write (set existing) — anything convertible into a Value works
cfg.set("ports.[0]", 8080).unwrap();
assert_eq!(cfg["ports.[0]"], 8080);
// Write (auto-creates intermediate groups)
cfg.set("database.credentials.username", "admin").unwrap();
assert_eq!(cfg["database.credentials.username"], "admin");
// Remove (returns the removed value)
let mut cfg = Config::from_str(r#"a = 1; b = 2; c = 3;"#).unwrap();
let removed = cfg.remove("b");
assert_eq!(removed, Some(Value::Integer(2)));
assert!(cfg.lookup("b").is_none());
// Remove array element (subsequent elements shift down)
let mut cfg = Config::from_str(r#"items = [10, 20, 30];"#).unwrap();
cfg.remove("items.[0]").unwrap();
assert_eq!(cfg["items.[0]"], 20);
```
## Format Features
- Top-level implicit groups (no outer braces required)
- Case-insensitive booleans (`true`, `True`, `TRUE`, `FaLsE`)
- Adjacent string concatenation (`"hello" " world"` -> `"hello world"`)
- Comments: `#`, `//`, `/* */`
- Setting separators: semicolons, commas, or whitespace (all optional)
- Both `=` and `:` as key-value delimiters
- `@include "file.cfg"` directives (via `Config::from_str` / `Config::from_file`,
with `*_with_include_dir` variants to set the include directory)
## Spec Conformance
Parsing and serialization follow the
[libconfig manual](https://hyperrealm.github.io/libconfig/libconfig_manual.html)
(v1.8.2), including its Appendix B grammar:
- The document root is a setting list (`<configuration> ::= <setting-list> |
<empty>`); top-level braces are rejected on parse and never written —
braces delimit nested groups only
- Setting names must match `[A-Za-z*][-A-Za-z0-9_*]*` (validated on read and write)
- Duplicate setting names within a group are rejected
- Arrays may only contain scalar values of a single type; anything else
belongs in a list (serialization picks `[...]` vs `(...)` automatically)
- Out-of-range integer conversions and float-to-integer coercions are errors,
not silent truncations; non-finite floats refuse to serialize
- Strings must be double-quoted; `\xNN` escapes take exactly two hex digits
The manual (including its Appendix B grammar) is the sole authority for this
crate's behavior. Output has additionally been cross-checked against the C
libconfig library as a comparison point; where an installed C version
disagrees with the manual (e.g. pre-1.8 versions lacking binary/octal
literals, trailing commas, or 64-bit auto-promotion), the manual wins.
Documented deviations: `\xNN` escapes ≥ 0x80 map to Unicode code points
(Rust strings cannot hold bare bytes), integers are accepted where floats are
expected (lossless), and the serde-level `from_str` does not process
`@include` directives (use `Config` for that).
## Examples
```bash
cargo run --example basic # Typed serialization/deserialization round-trip
cargo run --example config # Dynamic Config type: get/set all libconfig types
cargo run --example streaming # Tokio TCP stream of libconfig packets
```
## License
MIT OR Apache-2.0