metrale_bench/
param_text.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-26: Text ↔ [`ParamValues`]: the conversions a run record
4//! (`history.rs`) and a `--param` flag need.
5//!
6//! A stored run describes its whole configuration as text. Text goes back
7//! through [`crate::params::ParamKind::parse`], the domain check the edit box
8//! uses, so an old value that is no longer legal is reported, not accepted.
9//!
10//! Owner: bench (parameters).
11//! Invariants:
12//! - [`ParamValues::from_overrides`] refuses an unknown key and an
13//!   out-of-domain value; it never returns a partial set.
14
15use std::collections::BTreeMap;
16
17use anyhow::{Result, bail};
18
19use crate::params::{ParamSpec, ParamValue, ParamValues};
20
21impl ParamValues {
22    /// 2026-09-26: Every `(key, value)` pair, in key order (the map is a
23    /// `BTreeMap`), so records built from the same values diff cleanly.
24    pub fn iter(&self) -> impl Iterator<Item = (&str, &ParamValue)> {
25        self.0.iter().map(|(k, v)| (k.as_str(), v))
26    }
27
28    /// 2026-09-26: The whole configuration as text, for persistence.
29    pub fn to_strings(&self) -> BTreeMap<String, String> {
30        self.iter()
31            .map(|(k, v)| (k.to_string(), v.to_edit_string()))
32            .collect()
33    }
34
35    /// 2026-09-26: Schema defaults with `overrides` applied on top.
36    ///
37    /// Every value is routed through [`ParamKind::parse`], so an out-of-domain
38    /// override fails here rather than mid-run. An unknown key is an error
39    /// naming the valid ones: an ignored `--param` typo would measure
40    /// something other than what was asked for.
41    ///
42    /// [`ParamKind::parse`]: crate::params::ParamKind::parse
43    pub fn from_overrides<'a>(
44        specs: &[ParamSpec],
45        overrides: impl IntoIterator<Item = (&'a str, &'a str)>,
46    ) -> Result<Self> {
47        let mut values = Self::defaults(specs);
48        for (key, raw) in overrides {
49            let Some(spec) = specs.iter().find(|s| s.key == key) else {
50                let known: Vec<&str> = specs.iter().map(|s| s.key).collect();
51                bail!(
52                    "unknown parameter {key:?} — this benchmark takes: {}",
53                    known.join(", ")
54                );
55            };
56            let value = spec
57                .kind
58                .parse(raw)
59                .map_err(|e| anyhow::anyhow!("{}: {e}", spec.label))?;
60            values.set(key, value);
61        }
62        Ok(values)
63    }
64}
65
66#[cfg(test)]
67#[path = "param_text_tests.rs"]
68mod tests;