1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// SPDX-License-Identifier: MIT OR Apache-2.0
//! 2026-09-26: Strict parsing of the numeric process-scoped `METRALE_*`
//! settings: the rate limits (`rate_limiter`), the response store and the
//! conversation store.
//!
//! Owner: server config.
//! Invariants: `parse_min` never substitutes a default. Unset or blank is
//! `Ok(None)`; any other value that does not parse, or parses below `min`, is
//! `Err`.
//!
//! A lenient read like this one turns a typo into the default, and the
//! default rate limit, 0, means no limit:
//!
//! ```ignore
//! let rpm = std::env::var("METRALE_RATE_LIMIT_RPM")
//! .ok()
//! .and_then(|s| s.parse().ok()) // ← a typo lands here
//! .unwrap_or(0); // ← and silently becomes "off"
//! ```
//!
//! [`parse_min`] takes the raw value rather than reading the environment, so
//! tests call it without `set_var`, which is process-global. Each caller's
//! `from_env` reads the variable. `Carried::from_env`
//! (`main_modules/serve_load.rs`) runs all three before the listener binds,
//! and `serve` returns its error, so a bad value stops startup.
use std::fmt::Display;
use std::str::FromStr;
/// 2026-09-26: Parse an optional numeric setting, trimmed at both ends.
///
/// Returns `Ok(None)` when `raw` is unset, empty or whitespace-only; the caller
/// applies its default. A value that does not parse, or parses below `min`, is
/// an `Err` whose message names `key`, quotes `raw`, and states `meaning` and
/// `min`.
pub fn parse_min<T>(
key: &str,
raw: Option<&str>,
min: T,
meaning: &str,
) -> Result<Option<T>, String>
where
T: FromStr + PartialOrd + Display + Copy,
{
let Some(raw) = raw else { return Ok(None) };
let trimmed = raw.trim();
if trimmed.is_empty() {
return Ok(None);
}
let parsed: T = trimmed
.parse()
.map_err(|_| describe(key, raw, min, meaning, "is not a whole number"))?;
if parsed < min {
return Err(describe(
key,
raw,
min,
meaning,
"is below the smallest value this setting accepts",
));
}
Ok(Some(parsed))
}
/// 2026-09-26: The error text of `parse_min`, in the what / why / fix shape of
/// `cli::validate`'s `Violation`.
fn describe<T: Display>(key: &str, raw: &str, min: T, meaning: &str, problem: &str) -> String {
format!(
"{key}={raw:?} {problem}.\n \
why: {meaning} — expected a whole number >= {min}.\n \
fix: correct the value, or unset {key} to use the built-in default. \
It is NOT ignored: the server refuses to start rather than serve a \
configuration you did not ask for."
)
}
#[cfg(test)]
#[path = "env_config_tests.rs"]
mod tests;