A configuration is represented by a plain Rust struct. To take advantage of
OrthoConfig, derive the following traits:
-
serde::Deserializeandserde::Serialize– required for deserializing values and merging overrides. -
The derive macro generates a hidden
clap::Parserimplementation, so manualclapannotations are not required in typical use. CLI customization is performed usingortho_configattributes such ascli_short, orcli_long. -
OrthoConfig– provided by the library. This derive macro generates the code to load and merge configuration from multiple sources.
Optionally, the struct can include a #[ortho_config(prefix = "PREFIX")]
attribute. The prefix sets a common string for environment variables and
configuration file names. When the attribute omits a trailing underscore,
ortho_config appends one automatically so environment variables consistently
use <PREFIX>_. Trailing underscores are trimmed and the prefix is lower‑cased
when used to form file names. For example, a prefix of APP results in
environment variables like APP_PORT and file names such as .app.toml.
Field-level attributes
Field attributes modify how a field is sourced or merged:
| Attribute | Behaviour |
|---|---|
default = expr |
Supplies a default value when no source provides one. The expression can be a literal or a function path. |
cli_long = "name" |
Overrides the automatically generated long CLI flag (kebab-case). |
cli_short = 'c' |
Adds a single-letter short flag for the field. |
merge_strategy = "append" |
For Vec<T> fields, specifies that values from different sources should be concatenated. This is currently the only supported strategy and is the default for vector fields. |
Unrecognized keys are ignored by the derive macro for forwards compatibility.
Unknown keys will therefore silently do nothing. Developers who require
stricter validation may add manual compile_error! guards.
Vector append buffers operate on raw JSON values, so element types only need to
implement serde::Deserialize. Deriving serde::Serialize remains useful when
applications serialize configuration back out (for example, to emit defaults),
but it is no longer required merely to opt into the append strategy.
By default, each field receives a long flag derived from its name in kebab‑case and a short flag. The macro chooses the short flag using these rules:
- Use the field's first ASCII alphanumeric character.
- If that character is already taken or reserved, try its uppercase form.
- If both are unavailable, no short flag is assigned; specify
cli_shortto resolve the collision.
| Scenario | Result |
|---|---|
| First letter free | -p |
| Lowercase taken; uppercase free | -P |
| Both cases taken | none (set cli_short) |
Explicit override via cli_short |
-r |
Collisions are evaluated against short flags already assigned within the same
parser, and reserved characters such as clap's -h and -V. A character is
considered taken if it matches either set.
The macro does not scan other characters in the field name when deriving the
short flag. Short flags must be single ASCII alphanumeric characters and may
not use clap's global -h or -V options. Long flags must contain only ASCII
alphanumeric characters or hyphens, must not start with -, cannot be named
help or version, and the macro rejects underscores.
For example, when multiple fields begin with the same character, cli_short
can disambiguate the final field:
#[derive(OrthoConfig)]
struct Options {
port: u16, // -p
path: String, // -P
#[ortho_config(cli_short = 'r')]
peer: String, // -r via override
}
Example configuration struct
The following example illustrates many of these features:
use ortho_config::{OrthoConfig, OrthoError};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, OrthoConfig)]
// env vars use APP_ (the macro adds the underscore automatically)
#[ortho_config(prefix = "APP")]
struct AppConfig {
/// Logging verbosity
log_level: String,
/// Port to bind on – defaults to 8080 when unspecified
#[ortho_config(default = 8080)]
port: u16,
/// Optional list of features. Values from files, environment and CLI are appended.
#[ortho_config(merge_strategy = "append")]
features: Vec<String>,
/// Nested configuration for the database. A separate prefix is used to avoid ambiguity.
#[serde(flatten)]
database: DatabaseConfig,
/// Enable verbose output; also available as -v via cli_short
#[ortho_config(cli_short = 'v')]
verbose: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, OrthoConfig)]
#[ortho_config(prefix = "DB")] // used in conjunction with APP_ prefix to form APP_DB_URL
struct DatabaseConfig {
url: String,
#[ortho_config(default = 5)]
pool_size: Option<u32>,
}
fn main() -> Result<(), OrthoError> {
// Parse CLI arguments and merge with defaults, file and environment
let config = AppConfig::load()?;
println!("Final config: {:#?}", config);
Ok(())
}
clap attributes are not required in general; flags are derived from field
names and ortho_config attributes. In this example, the AppConfig struct
uses a prefix of APP. The DatabaseConfig struct declares a prefix DB,
resulting in environment variables such as APP_DB_URL. The features field
is a Vec<String> and accumulates values from multiple sources rather than
overwriting them.
Customising configuration discovery
Configuration discovery can be tailored per struct using the discovery(...)
attribute. The keys recognised today include:
app_name: directory name used under XDG and application data folders.env_var: override for the environment variable consulted before discovery runs (defaults to<PREFIX>CONFIG_PATH).config_file_name: primary filename searched in platform-specific configuration directories (defaults toconfig.toml).dotfile_name: dotfile name consulted in the current working directory and the user's home directory.project_file_name: filename searched within project roots (defaults to the dotfile name).config_cli_long/config_cli_short: rename the CLI flag used to provide an explicit configuration path.config_cli_visible: whentrue, the generated CLI flag appears in help output instead of remaining hidden.
Supplying only the keys you need lets you rename the CLI flag without altering file discovery, or vice versa. When the attribute is omitted, the defaults described in Config path override continue to apply.