rtb-config 0.6.1

Layered, typed configuration backed by figment. Part of the phpboyscout Rust toolkit.
Documentation
---
title: rtb-config
description: Typed, layered configuration via figment — embedded defaults, user files, and env vars merged into your Deserialize struct with atomic reload.
---

# `rtb-config`

Typed, layered configuration backed by [figment]. `Config<C>` is a
generic container over *your* `serde::Deserialize` struct — populated by
layering sources through figment and snapshot-swapped atomically via
[arc-swap].

Part of the [phpboyscout Rust toolkit](https://rust.phpboyscout.uk);
extracted from — and battle-tested by —
[rust-tool-base](https://gitlab.com/phpboyscout/rust-tool-base).

## Typed, not dynamic

Many config layers expose dynamic accessors — `GetString("foo.bar")`
style. `rtb-config` deliberately rejects that pattern. Rust gives us
compile-time checking for free: declare a struct, derive `Deserialize`,
and let `cargo check` catch every mistyped field across every call site.
Hierarchical access uses nested structs; there is no `Sub()` or
`get_string()` API.

```rust
use rtb_config::Config;
use serde::Deserialize;

#[derive(Deserialize, Default)]
struct MyConfig { host: String, port: u16 }

let cfg = Config::<MyConfig>::builder()
    .embedded_default("host: localhost\nport: 8080\n")
    .env_prefixed("MYTOOL_")
    .build()?;

let current = cfg.get();
assert_eq!(current.host, "localhost");
```

## Layered precedence

Sources merge last-wins:

**embedded default → user file → env vars (`<PREFIX>_*`)**

So `MYTOOL_PORT=9999` beats `port: 9090` in the user file, which beats
`port: 8080` in the embedded default. Env prefixes are configured with
`.split("_")`, so `MYTOOL_HTTP_PORT=8080` populates a nested
`http.port` field. Missing files are **not** an error — figment treats
absent files as empty sources; a path that exists but is not a regular
file surfaces as `ConfigError::Io`.

## Atomic reload and subscriptions

`Config::reload()` re-reads every source and atomically swaps the stored
value via `arc_swap::ArcSwap`. Readers holding a pre-reload `Arc<C>`
snapshot keep their consistent view until they ask for a new one —
snapshots never tear, and the read path takes no locks.
`Config::subscribe()` returns a `tokio::sync::watch::Receiver` that
wakes on every successful reload.

## Features

| Feature | Adds | Cost |
|---|---|---|
| `hot-reload` | `Config::watch_files()` — a debounced background watcher (notify) that calls `reload()` on change and hands back a `WatchHandle` to stop it. | `notify`, `notify-debouncer-full` |
| `mutable` | `Config::schema()` (JSON Schema for `C` via schemars) and `Config::write()` (write the current value to YAML / TOML / JSON, chosen by extension). | `schemars`, `serde_json`, `serde_yaml`, `toml` |

Both are default-off so tools that don't need them don't pay the
dependency weight.

Full API reference: [docs.rs/rtb-config](https://docs.rs/rtb-config).

## Roadmap

A v2 **Store architecture** — value provenance (which layer supplied
each field) and structure-preserving writes (edits that keep the user
file's comments and ordering intact) — is approved and will land in this
repository. See the
[Store-architecture spec](https://gitlab.com/phpboyscout/rust-tool-base/-/blob/main/docs/development/specs/2026-07-23-rtb-config-v2-store-architecture.md).
The figment-based v1 documented here remains the supported surface until
then.

## Design record

The authoritative contracts are the crate's specs, retained in the
rust-tool-base spec series:

- [v0.1 — typed layered container + explicit reload]https://gitlab.com/phpboyscout/rust-tool-base/-/blob/main/docs/development/specs/2026-04-22-rtb-config-v0.1.md
- [v0.2 — hot reload + subscribe]https://gitlab.com/phpboyscout/rust-tool-base/-/blob/main/docs/development/specs/2026-04-24-rtb-config-hot-reload.md

Concept-level background:
[Configuration](https://gitlab.com/phpboyscout/rust-tool-base/-/blob/main/docs/concepts/configuration.md)
in the rust-tool-base docs.

[figment]: https://crates.io/crates/figment
[arc-swap]: https://crates.io/crates/arc-swap