Error handling

Version 0.7.0 Updated Jan 02, 2026

load and load_and_merge_subcommand_for return OrthoResult<T>, an alias for Result<T, Arc<OrthoError>>. OrthoError wraps errors from clap, file I/O and figment. Failures during the final merge of CLI values over configuration sources surface as the Merge variant, providing clearer diagnostics when the combined data is invalid. When multiple sources fail, the errors are collected into the Aggregate variant so callers can inspect each individual failure. Consumers should handle these errors appropriately, for example by printing them to stderr and exiting. If required fields are missing after merging, the crate returns OrthoError::MissingRequiredValues with a user‑friendly list of missing paths and hints on how to provide them. For example:

Missing required values:
  sample_value (use --sample-value, SAMPLE_VALUE, or file entry)

Preserving `clap` display exits

When a user passes --help or --version, clap surfaces specialised ErrorKind::DisplayHelp / DisplayVersion errors so applications can print usage text and exit successfully. Deriving OrthoConfig often goes hand in hand with Cli::try_parse() so applications can map errors into their own types. Before performing that conversion, call ortho_config::is_display_request to detect these cases and delegate to err.exit():

use clap::Parser;
use ortho_config::{is_display_request, OrthoConfig};

fn parse_cli() -> Result<MyCli, CliError> {
    match MyCli::try_parse() {
        Ok(cli) => Ok(cli),
        Err(mut err) => {
            if is_display_request(&err) {
                err.exit();
            }
            Err(CliError::ArgumentParsing(err.into()))
        }
    }
}

The examples/hello_world crate applies this pattern in main.rs. Behavioural tests assert that both --help and --version exit with code 0 so regressions are caught automatically.

Aggregating multiple errors

To return multiple errors in one go, use OrthoError::aggregate. It accepts any iterator of items that can be converted into Arc<OrthoError> so both owned and shared errors are supported. If the list might be empty, OrthoError::try_aggregate returns Option<OrthoError> instead of panicking:

use std::sync::Arc;
use ortho_config::OrthoError;

// From bare errors
let err = OrthoError::aggregate(vec![
    OrthoError::Validation { key: "port".into(), message: "must be positive".into() },
    OrthoError::gathering(figment::Error::from("invalid")),
]);

// From shared errors
let err = OrthoError::aggregate(vec![
    Arc::new(OrthoError::Validation { key: "x".into(), message: "bad".into() }),
    OrthoError::gathering_arc(figment::Error::from("boom")),
]);

Gathering vs Merge errors

OrthoConfig distinguishes between two phases of configuration loading:

  • Gathering (OrthoError::Gathering): Errors that occur while reading configuration sources (files, environment variables). These indicate problems with the source data itself, such as malformed TOML or invalid JSON.

  • Merge (OrthoError::Merge): Errors that occur while combining layers and deserializing the final configuration. These indicate incompatibilities between the merged data and the target struct, such as type mismatches or invalid field values.

When deserializing the final merged configuration fails (for example, because a field has an invalid type after all layers are combined), the error is reported as Merge. This distinction helps diagnose whether an issue lies with a specific source file (Gathering) or with the combined result of all layers (Merge).

Mapping errors ergonomically

To reduce boiler‑plate when converting between error types, the crate exposes small extension traits:

  • OrthoResultExt::into_ortho() converts Result<T, E> into OrthoResult<T> when E: Into<OrthoError> (e.g., serde_json::Error).
  • OrthoMergeExt::into_ortho_merge() converts Result<T, figment::Error> into OrthoResult<T> as OrthoError::Merge.
  • OrthoJsonMergeExt::into_ortho_merge_json() converts Result<T, serde_json::Error> into OrthoResult<T> as OrthoError::Merge, preserving location information from the JSON parser.
  • IntoFigmentError::into_figment() converts Arc<OrthoError> (or &Arc<OrthoError>) into figment::Error for interop in tests or adapters, cloning the inner error to preserve structured details where possible.
  • ResultIntoFigment::to_figment() converts OrthoResult<T> into Result<T, figment::Error>.

Examples:

use ortho_config::{OrthoMergeExt, OrthoResultExt, ResultIntoFigment};

fn sanitize<T: serde::Serialize>(v: &T) -> ortho_config::OrthoResult<serde_json::Value> {
    serde_json::to_value(v).into_ortho()
}

fn extract(fig: figment::Figment) -> ortho_config::OrthoResult<MyCfg> {
    fig.extract::<MyCfg>().into_ortho_merge()
}

fn interop(r: ortho_config::OrthoResult<MyCfg>) -> Result<MyCfg, figment::Error> {
    r.to_figment()
}