confroid 0.0.1

The n+1-st config reader for your environment-based configs.
Documentation
//! Reading enum-valued configuration.
//!
//! An enum is just a scalar: implement `FromStr` for it and register it with
//! `confroid::from_str_scalar!`. Run it with, for example:
//!
//! ```sh
//! LOG_LEVEL=debug MODE=cluster cargo run -p confroid --example enum_config
//! ```

use std::fmt;
use std::str::FromStr;

/// How chatty the logs are.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl FromStr for LogLevel {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "error" => Ok(Self::Error),
            "warn" | "warning" => Ok(Self::Warn),
            "info" => Ok(Self::Info),
            "debug" => Ok(Self::Debug),
            "trace" => Ok(Self::Trace),
            other => Err(format!(
                "unknown log level `{other}` (expected error|warn|info|debug|trace)"
            )),
        }
    }
}

impl fmt::Display for LogLevel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::Error => "error",
            Self::Warn => "warn",
            Self::Info => "info",
            Self::Debug => "debug",
            Self::Trace => "trace",
        })
    }
}

/// Which deployment mode to run in.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
    Standalone,
    Cluster,
}

impl FromStr for Mode {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_ascii_lowercase().as_str() {
            "standalone" => Ok(Self::Standalone),
            "cluster" => Ok(Self::Cluster),
            other => Err(format!(
                "unknown mode `{other}` (expected standalone|cluster)"
            )),
        }
    }
}

impl fmt::Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::Standalone => "standalone",
            Self::Cluster => "cluster",
        })
    }
}

// Teach confroid to read each enum from a single variable via its `FromStr`.
confroid::from_str_scalar!(LogLevel, Mode);

#[derive(confroid::Config, Debug)]
#[allow(dead_code)] // fields are read via Debug in `main`
struct Config {
    /// Logging verbosity.
    #[confroid(default = LogLevel::Info, example = "debug")]
    log_level: LogLevel,
    /// Deployment mode.
    #[confroid(default = Mode::Standalone)]
    mode: Mode,
}

fn main() {
    // Read from the process environment (falling back to the defaults).
    match confroid::from_env::<Config>() {
        Ok(config) => println!("from environment: {config:?}"),
        Err(err) => eprintln!("config error: {err}"),
    }

    // A self-contained demonstration using explicit values:
    let config: Config =
        confroid::from_pairs([("LOG_LEVEL", "debug"), ("MODE", "cluster")]).expect("valid config");
    println!("explicit:        {config:?}");

    let defaults: Config =
        confroid::from_pairs(Vec::<(&str, &str)>::new()).expect("defaults are total");
    println!("all defaults:    {defaults:?}");
}