clap-config-fallback
Add configuration-file fallback to clap without losing clap parsing, validation, or error handling.
Why?
clap is excellent at parsing CLI arguments and producing high-quality diagnostics.
What it does not do out of the box is merge a config file with CLI arguments while still enforcing the same clap contract.
Common alternatives often:
- reimplement parsing and drift from clap behavior,
- duplicate structs (one for CLI, one for config), or
- validate after parsing in a separate pass.
clap-config-fallback keeps clap as the single source of truth.
How it works
#[derive(ConfigParser)] generates an intermediate optional Opts struct and runs a two-phase parse:
- Parse CLI arguments into
Opts. - Resolve a config file path (if one is defined).
- Load and deserialize config into a generated
Configstruct. - Merge values with precedence CLI > config.
- Reconstruct synthetic CLI args from merged
Opts. - Run your original clap parser for final parsing + validation.
Because the final pass is still clap, you keep clap’s errors and validation behavior.
Precedence and edge cases
- CLI always wins over config for the same field.
- Config fallback only runs when a
#[config(path)]field is present and resolves toSome(path). - If the path field has a clap
default_value, that default also enables fallback. - If no path is available, parsing behaves like normal clap parsing.
- A non-existent file is reported as a clap
Ioerror. - Unknown/unsupported format (or feature-disabled format) is reported as a clap
InvalidValueerror.
Requirements
Using ConfigParser has a few constraints:
- The target type must be a named struct.
- Fields that participate in fallback must be serializable/deserializable.
- Field values must be representable as CLI argument values (
to_string()output is used). - Nested flattened structs must also derive
ConfigParser.
Known limitations
#[command(subcommand)]is not supported yet.- Some advanced clap attribute combinations may not be fully covered.
- Only file-based configuration is currently supported.
Example
use Parser;
use ConfigParser;
ConfigParser attributes
#[config(path)]
Marks the field that stores the configuration file path.
- Optional (you may omit it entirely).
- Must be one of:
StringOption<String>
If no path field exists, config fallback is disabled.
#[config(format = "...")]
Forces how the config file is parsed for the #[config(path)] field.
Supported values:
tomlyamljsonauto(default; detect by extension)
#[config(skip)]
Excludes a field from config fallback while keeping normal CLI parsing.
port: u16,
#[config(skip_all)]
Disables config fallback for all fields in the struct.
#[config(value_format = ...)]
Defines how a field value is converted into a String when rebuilding CLI arguments from the merged
inputs.
This conversion happens after merging CLI arguments and configuration values. It controls how the final value is serialized back into a CLI-compatible string.
The expression must evaluate to a String.
The field identifier is available in scope, such as:
duration: Duration,
Notes
- The expression is evaluated on a reference to the field value.
- This is the counterpart of clap’s
value_parser, but for the reverse direction (value → string). - If omitted, the default behavior uses
ToString.