Skip to main content

enum_config/
enum_config.rs

1//! Reading enum-valued configuration.
2//!
3//! An enum is just a scalar: implement `FromStr` for it and derive
4//! `confroid::ConfigValue`. Run it with, for example:
5//!
6//! ```sh
7//! LOG_LEVEL=debug MODE=cluster cargo run -p confroid --example enum_config
8//! ```
9
10use std::fmt;
11use std::str::FromStr;
12
13#[derive(Debug)]
14struct ParseEnumError(String);
15
16impl fmt::Display for ParseEnumError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        f.write_str(&self.0)
19    }
20}
21
22impl std::error::Error for ParseEnumError {}
23
24/// How chatty the logs are.
25#[derive(confroid::ConfigValue, Debug, Clone, Copy, PartialEq, Eq)]
26enum LogLevel {
27    Error,
28    Warn,
29    Info,
30    Debug,
31    Trace,
32}
33
34impl FromStr for LogLevel {
35    type Err = ParseEnumError;
36    fn from_str(s: &str) -> Result<Self, Self::Err> {
37        match s.to_ascii_lowercase().as_str() {
38            "error" => Ok(Self::Error),
39            "warn" | "warning" => Ok(Self::Warn),
40            "info" => Ok(Self::Info),
41            "debug" => Ok(Self::Debug),
42            "trace" => Ok(Self::Trace),
43            other => Err(ParseEnumError(format!(
44                "unknown log level `{other}` (expected error|warn|info|debug|trace)"
45            ))),
46        }
47    }
48}
49
50impl fmt::Display for LogLevel {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.write_str(match self {
53            Self::Error => "error",
54            Self::Warn => "warn",
55            Self::Info => "info",
56            Self::Debug => "debug",
57            Self::Trace => "trace",
58        })
59    }
60}
61
62/// Which deployment mode to run in.
63#[derive(confroid::ConfigValue, Debug, Clone, Copy, PartialEq, Eq)]
64enum Mode {
65    Standalone,
66    Cluster,
67}
68
69impl FromStr for Mode {
70    type Err = ParseEnumError;
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        match s.to_ascii_lowercase().as_str() {
73            "standalone" => Ok(Self::Standalone),
74            "cluster" => Ok(Self::Cluster),
75            other => Err(ParseEnumError(format!(
76                "unknown mode `{other}` (expected standalone|cluster)"
77            ))),
78        }
79    }
80}
81
82impl fmt::Display for Mode {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.write_str(match self {
85            Self::Standalone => "standalone",
86            Self::Cluster => "cluster",
87        })
88    }
89}
90
91#[derive(confroid::Config, Debug)]
92#[allow(dead_code)] // fields are read via Debug in `main`
93struct Config {
94    /// Logging verbosity.
95    #[confroid(default = LogLevel::Info, example = "debug")]
96    log_level: LogLevel,
97    /// Deployment mode.
98    #[confroid(default = Mode::Standalone)]
99    mode: Mode,
100}
101
102fn main() {
103    // Read from the process environment (falling back to the defaults).
104    match confroid::from_env::<Config>() {
105        Ok(config) => println!("from environment: {config:?}"),
106        Err(err) => eprintln!("config error: {err}"),
107    }
108
109    // A self-contained demonstration using explicit values:
110    let config: Config =
111        confroid::from_pairs([("LOG_LEVEL", "debug"), ("MODE", "cluster")]).expect("valid config");
112    println!("explicit:        {config:?}");
113
114    let defaults: Config =
115        confroid::from_pairs(Vec::<(&str, &str)>::new()).expect("defaults are total");
116    println!("all defaults:    {defaults:?}");
117}