Adapter types for declaratively loading configurations with clap
Did you know that any type that implements [FromStr
] can
be used in a clap
derive struct? That means that any
logic you can fit into a fn(&str) -> Result<T, Error>
can
be run at parsing-time. This can be expecially useful for
declaratively selecting config files or doing other cool
stuff. Check this out:
use clap::Parser;
use clap_adapters::prelude::*;
#[derive(Debug, Parser)]
struct Cli {
/// Path to a config file of arbitrary Json
#[clap(long)]
config: PathTo<JsonOf<serde_json::Value>>,
}
fn main() {
// Create a config file in a temporary directory
let config_dir = tempfile::tempdir()?;
let config_path = config_dir.path().join("config.json");
let config_path_string = config_path.display().to_string();
// Write a test config of {"hello":"world"} to the config file
let config = serde_json::json!({"hello": "world"});
let config_string = serde_json::to_string(&config)?;
std::fs::write(&config_path, &config_string)?;
// Parse our CLI, passing our config file path to --config
let cli = Cli::parse_from(["app", "--config", &config_path_string]);
let data = cli.config.data();
// We should expect the value we get to match what we wrote to the config
assert_eq!(data, &serde_json::json!({"hello":"world"}));
}
You can implement additional composable adapters by defining new types that
implement traits in this crate. For example, by implementing FromReader
,
you could define an adapter that can construct itself from a file path, nesting
your adapter into PathTo<T>
, like PathTo<YourAdapter>
.