Many CLIs have global options plus commands with different configuration. Derive
OrthoConfig for each subcommand's argument struct and merge only the
selected command:
use clap::{Parser, Subcommand};
use ortho_config::{OrthoConfig, OrthoResult, SubcmdConfigMerge};
use serde::{Deserialize, Serialize};
#[derive(Debug, Parser)]
#[command(name = "acme")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Serve(ServeConfig),
}
#[derive(Debug, Default, Parser, Deserialize, Serialize, OrthoConfig)]
#[command(name = "serve")]
#[ortho_config(prefix = "ACME_SERVE_")]
struct ServeConfig {
#[arg(long)]
port: Option<u16>,
}
fn main() -> OrthoResult<()> {
match Cli::parse().command {
Command::Serve(cli) => {
let config = cli.load_and_merge()?;
println!("port={:?}", config.port);
}
}
Ok(())
}
For an enum with many variants, derive SelectedSubcommandMerge and use
load_globals_and_merge_selected_subcommand. The generated match keeps the
entry point small. Add #[ortho_config(cli_default_as_absent)] to a field when
a clap default should not override a value supplied by a file or environment
variable.
When a subcommand needs the same hermetic merge boundary, pass a
SharedScanEnvSource to load_and_merge_with_sources instead of using the
process-backed load_and_merge. For the ServeConfig above, the call is:
Set ACME_SERVE_CMDS_SERVE_PORT to 9000, then bind the map with
let environment = Arc::new(
MapEnv::new().with_var("ACME_SERVE_CMDS_SERVE_PORT", "9000"));
and call let config = cli.load_and_merge_with_sources(environment)?;.
This supplies the ACME_SERVE_CMDS_SERVE_PORT layer while preserving the
existing CLI precedence. Import MapEnv and std::sync::Arc when using this
pattern.
Some commands only parse arguments and do not participate in configuration loading. Keep those arguments in a separate clap-only type:
use clap::Parser;
#[derive(Debug, Parser)]
struct ParseOnlyArgs {
#[arg(long)]
format: Option<String>,
}
let args = ParseOnlyArgs::parse();
println!("format={:?}", args.format);
This parse-only workflow calls clap directly and does not load files or merge
environment values. Because ParseOnlyArgs does not derive OrthoConfig, it
has no load_and_merge or source-aware merge API. Use the OrthoConfig-derived
ServeConfig above when a command needs configuration layering.