didbot_config/precedence.rs
1//! The one place the file, command-line arguments and the environment are
2//! resolved against each other.
3//!
4//! **Command-line arguments win, then the file, then the environment.**
5//! Arguments are the leakiest tier — world-readable in `/proc/<pid>/cmdline`
6//! — so this order is a concession to how a developer actually runs the
7//! binary, not an endorsement: a secret's own *value* should never be an
8//! argument in a real deployment, only a path, and this
9//! function resolving arguments highest does not change that — see the
10//! crate root's doc.
11//!
12//! Every caller in this workspace is expected to route its own three-way
13//! merges through this function rather than reimplementing the order field
14//! by field, so the order is one fact instead of one per setting.
15
16/// Resolves one setting across the three tiers, first `Some` wins.
17///
18/// The caller's own built-in default is not a fourth argument here on
19/// purpose: `None` from every tier means "nothing configured it," and what
20/// that becomes is specific to the setting (a fixed constant, a computed
21/// value, or an outright refusal to start) in a way this function has no
22/// business deciding.
23pub fn precedence<T>(cli: Option<T>, file: Option<T>, env: Option<T>) -> Option<T> {
24 cli.or(file).or(env)
25}
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30
31 #[test]
32 fn cli_beats_file_and_env() {
33 assert_eq!(precedence(Some(1), Some(2), Some(3)), Some(1));
34 }
35
36 #[test]
37 fn file_beats_env_when_cli_is_silent() {
38 assert_eq!(precedence(None, Some(2), Some(3)), Some(2));
39 }
40
41 #[test]
42 fn env_is_last_resort() {
43 assert_eq!(precedence(None, None, Some(3)), Some(3));
44 }
45
46 #[test]
47 fn nothing_configured_is_none() {
48 assert_eq!(precedence::<u32>(None, None, None), None);
49 }
50}