# congen
[](https://crates.io/crates/congen)
[](https://docs.rs/congen)
[](https://deps.rs/repo/github/Wasabi375/congen)
`congen` helps you build configuration systems that support partial updates from structured changes and CLI input.
It is designed around two ideas:
- your config type implements `Configuration`
- updates are represented by a companion `CongenChange` type
Most projects use the derive macros from `congen-derive`, so you usually do not need to write these trait impls manually.
## Feature overview
- Derive support for config structs via `#[derive(Configuration)]`.
- Automatic generation of a strongly typed change object (`<Config>Change`) for each config type.
- Field-level update verbs:
- `set`
- `unset` (for unsettable fields like `Option<_>` or `bool`)
- `use-default` (when defaults are configured)
- Nested updates by path (`sub.field`, `outer.inner.value`, etc.).
- Optional field semantics with `Option<T>` support.
- Collection updates for:
- `Vec<T>` (`append`, `update`, `remove`, `empty`)
- `HashMap<K, T>` (`append`, `update`, `remove`, `empty`)
- Typed list/map keys with support for string, signed, and unsigned map keys.
- Built-in primitive support, including:
- `bool`
- integer and float numeric primitives
- `String`, `CString`, and `OsString`
- `clap` bridge (`CongenClap<T>`) that generates subcommands from your configuration description.
- `ValueEnum` integration via `#[derive(ValueEnumConfiguration)]` for enum fields used as config values.
`clap` support is feature-gated behind the `clap` crate feature (enabled by default).
## Derive macros
`congen` re-exports derive macros from `congen-derive`:
- `Configuration` for structs
- `ValueEnumConfiguration` for `clap::ValueEnum` enums (requires the `clap` feature)
Example:
```rust
use congen::Configuration;
#[derive(Configuration, Debug)]
struct Config {
port: u16,
#[congen(default)]
log_file: Option<String>,
}
```
## Default values
This crate does not use rusts [Default] trait to provide default values. This is done, because a default
value for a type might not be necessarialy translate into a good default value in a configuration, e.g.
the default for all number types is zero, which is not a good default value for the number of threads
some process is allowed to use.
Instead [CongenInternal::default] is used instead. The exact default value can be configured using
the `#[congen(default)]` attribute.
- `#[congen(default)]` use `CongenInternal::default` for the field
- `#[congen(rust_default)]` use `Default::default` for the field
- `#[congen(default = <expr>)]` (custom expression)
In addition `#[congen(inner_default = <expr>)]` can be used to control the default value of inner types
in `Vec`, `HashMap` and `Option`.
## CLI integration with `CongenClap`
You can parse configuration changes with clap and apply them to an existing config instance.
## Typical application flow
The most common pattern is:
1. Read the current config from disk.
2. If the CLI command is `config`, parse and apply a `CongenChange`, then write the updated config back to disk.
3. Otherwise, run your normal program logic with the config that was read from disk.
`congen` is focused on step 2: creating and applying strongly typed config changes.
```rust
use clap::{Parser, Subcommand};
use congen::{Configuration, CongenClap, load_from_env};
#[derive(Configuration, Debug)]
struct Config {
retries: u32,
#[congen(default)]
token: Option<String>,
}
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Config(CongenClap<Config>),
Run,
}
fn main() {
let cli = Cli::parse_from(["program_name", "config", "token", "unset"]);
let mut config = read_config_from_file("config.toml");
match cli.command {
Commands::Config(change_args) => {
config.apply_change(change_args.into_change());
write_config_to_file("config.toml", &config);
}
Commands::Run => {
let config_change = load_from_env::<Config>("CONFIG").unwrap();
config.apply_change(config_change);
run_program(config);
}
}
}
fn read_config_from_file(_path: &str) -> Config {
Config {
retries: 3,
token: Some("abc".to_string()),
}
}
fn write_config_to_file(_path: &str, _config: &Config) {
// serialize and persist config
}
fn run_program(_config: Config) {
// your actual application logic
}
```
Typical generated commands look like:
- `config retries set 5`
- `config token unset`
- `config token use-default`
Nested fields and collections are represented through nested subcommands (see workspace examples).
## Environment variable integration
Besides clap-based parsing, you can build a `CongenChange` from environment variables using `congen::env`:
- `env::load_from_env::<Config>("CONFIG")` reads from `std::env::vars_os()`.
Environment variable names use this shape:
- `<PREFIX>_<PATH>_<VERB>`
Where:
- `<PATH>` is the config field path in screaming snake case (`sub.e` -> `SUB_E`, `myValue` -> `MY_VALUE`).
- `<VERB>` is one of:
- `SET` (value is parsed and applied)
- `UNSET` (value is ignored)
- `USE_DEFAULT` (also accepts `USE-DEFAULT` and `USEDEFAULT`; value is ignored)
## Workspace examples
This repository contains complete examples:
- `examples/basic` - nested structs, `Option`, defaults, and basic set/unset behavior.
- `examples/lists` - `Vec`/`HashMap` operations, nested collection updates, key handling, and `inner_default`.
- `examples/primitives` - primitive parsing and updates across supported base types.
## Changelog
A changelog exists at [CHANGELOG.md](CHANGELOG.md)