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