Skip to main content

dynamo_truthy/
lib.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Canonical truthy/falsy flag parsing for Dynamo.
5//!
6//! Single owner of the boolean vocabulary accepted from user-supplied
7//! configuration (environment variables, HTTP headers, config values):
8//! truthy = `1 | true | on | yes`, falsy = `0 | false | off | no` or empty,
9//! case-insensitive, surrounding whitespace ignored.
10//!
11//! `dynamo_runtime::config` re-exports these helpers and is the canonical
12//! import path for crates that already depend on `dynamo-runtime`. Crates that
13//! cannot (e.g. `dynamo-memory`, `dynamo-kv-router`, `dynamo-mocker`) depend on
14//! this crate directly. Do not hand-roll new bool parsers — a divergent
15//! accepted set means `SOMEFLAG=on` works for one flag and silently not
16//! another. `tests/no_bool_parse_forks.rs` greps the workspace for forks.
17
18/// Check if a string is truthy: `1 | true | on | yes`, case-insensitive,
19/// surrounding whitespace ignored. Everything else is not truthy.
20pub fn is_truthy(val: &str) -> bool {
21    matches!(
22        val.trim().to_lowercase().as_str(),
23        "1" | "true" | "on" | "yes"
24    )
25}
26
27/// Check if a string is falsey: `0 | false | off | no` or empty,
28/// case-insensitive, surrounding whitespace ignored.
29pub fn is_falsey(val: &str) -> bool {
30    matches!(
31        val.trim().to_lowercase().as_str(),
32        "" | "0" | "false" | "off" | "no"
33    )
34}
35
36/// Parse a string as a boolean value, returning an error if it is neither
37/// truthy nor falsey.
38///
39/// # Returns
40/// * `Ok(true)` - for truthy values ([`is_truthy`])
41/// * `Ok(false)` - for falsey values ([`is_falsey`]), including empty
42/// * `Err(_)` - for any other value
43pub fn parse_bool(val: &str) -> anyhow::Result<bool> {
44    if is_truthy(val) {
45        Ok(true)
46    } else if is_falsey(val) {
47        Ok(false)
48    } else {
49        anyhow::bail!(
50            "Invalid boolean value: '{}'. Expected one of: true/false, 1/0, on/off, yes/no",
51            val
52        )
53    }
54}
55
56/// Tri-state parse for call sites that preserve their own default unless the
57/// value is a deliberate boolean choice.
58///
59/// Unlike [`parse_bool`], an empty (or whitespace-only) value yields `None` —
60/// a variable declared without a value (common in Kubernetes manifests and
61/// Docker Compose files) must not override a default — and so does any
62/// unrecognized value.
63///
64/// # Returns
65/// * `Some(true)` - for truthy values ([`is_truthy`])
66/// * `Some(false)` - for `0 | false | off | no`
67/// * `None` - for empty or unrecognized values
68pub fn parse_bool_opt(val: &str) -> Option<bool> {
69    if is_truthy(val) {
70        Some(true)
71    } else if val.trim().is_empty() {
72        None
73    } else if is_falsey(val) {
74        Some(false)
75    } else {
76        None
77    }
78}
79
80/// Check if an environment variable is set to a truthy value.
81/// Unset (or non-unicode) variables are not truthy.
82pub fn env_is_truthy(env: &str) -> bool {
83    match std::env::var(env) {
84        Ok(val) => is_truthy(val.as_str()),
85        Err(_) => false,
86    }
87}
88
89/// Check if an environment variable is set to a falsey value (including set
90/// but empty). Unset (or non-unicode) variables are not falsey.
91pub fn env_is_falsey(env: &str) -> bool {
92    match std::env::var(env) {
93        Ok(val) => is_falsey(val.as_str()),
94        Err(_) => false,
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    const TRUTHY: &[&str] = &[
103        "1", "true", "TRUE", "True", "on", "ON", "On", "yes", "YES", "Yes", " true ", "\tyes\n",
104    ];
105    const FALSEY: &[&str] = &[
106        "0", "false", "FALSE", "False", "off", "OFF", "Off", "no", "NO", "No", "", "  ", " false ",
107    ];
108    const NEITHER: &[&str] = &[
109        "2", "enabled", "disabled", "maybe", "y", "n", "t", "f", "-1",
110    ];
111
112    #[test]
113    fn truthy_spellings() {
114        for val in TRUTHY {
115            assert!(is_truthy(val), "value={val:?}");
116            assert!(!is_falsey(val), "value={val:?}");
117            assert!(parse_bool(val).unwrap(), "value={val:?}");
118        }
119    }
120
121    #[test]
122    fn falsey_spellings() {
123        for val in FALSEY {
124            assert!(is_falsey(val), "value={val:?}");
125            assert!(!is_truthy(val), "value={val:?}");
126            assert!(!parse_bool(val).unwrap(), "value={val:?}");
127        }
128    }
129
130    #[test]
131    fn invalid_spellings() {
132        for val in NEITHER {
133            assert!(!is_truthy(val), "value={val:?}");
134            assert!(!is_falsey(val), "value={val:?}");
135            assert!(parse_bool(val).is_err(), "value={val:?}");
136        }
137    }
138
139    #[test]
140    fn parse_bool_opt_spellings() {
141        for val in TRUTHY {
142            assert_eq!(parse_bool_opt(val), Some(true), "value={val:?}");
143        }
144        for val in FALSEY {
145            let expected = if val.trim().is_empty() {
146                None // declared-but-empty is not a deliberate choice
147            } else {
148                Some(false)
149            };
150            assert_eq!(parse_bool_opt(val), expected, "value={val:?}");
151        }
152        for val in NEITHER {
153            assert_eq!(parse_bool_opt(val), None, "value={val:?}");
154        }
155    }
156
157    #[test]
158    fn env_helpers() {
159        // Each test uses its own variable name: tests run concurrently and
160        // the process environment is shared.
161        const UNSET: &str = "DYN_TRUTHY_TEST_UNSET";
162        assert!(!env_is_truthy(UNSET));
163        assert!(!env_is_falsey(UNSET));
164
165        const SET: &str = "DYN_TRUTHY_TEST_SET";
166        for (val, truthy, falsey) in [("on", true, false), ("off", false, true), ("", false, true)]
167        {
168            // SAFETY: single-threaded with respect to this variable name.
169            unsafe { std::env::set_var(SET, val) };
170            assert_eq!(env_is_truthy(SET), truthy, "value={val:?}");
171            assert_eq!(env_is_falsey(SET), falsey, "value={val:?}");
172        }
173    }
174}