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;