//! The leaf value type flowing through substitution and filters. use std::fmt; /// A leaf value: the result of resolving a `{{ path }}` before and after /// filters run. Callers populate a [`crate::Substituter`] with these and read /// them back out with `get`. #[derive(Debug, Clone, PartialEq)] pub enum Value { Int(i64), Float(f64), String(String), } impl Value { /// Coerce to `f64` for numeric filters. `Int` is widened; `String` returns `None`. pub fn as_f64(&self) -> Option { match self { Self::Int(n) => Some(*n as f64), Self::Float(x) => Some(*x), Self::String(_) => None, } } } impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Int(n) => write!(f, "{n}"), Self::Float(x) => f.write_str(&format_float(*x)), Self::String(s) => f.write_str(s), } } } /// Format a float with up to 4 decimal places, trimming trailing zeros. /// /// `22.0 -> "22"`, `0.59 -> "0.59"`, `0.0367 -> "0.0367"`, `61960.0 -> "61960"`. fn format_float(x: f64) -> String { let s = format!("{x:.4}"); let trimmed = s.trim_end_matches('0').trim_end_matches('.'); trimmed.to_string() } #[cfg(test)] mod tests { use super::*; #[test] fn float_formatting_strips_trailing_zeros() { assert_eq!(format_float(22.0), "22"); assert_eq!(format_float(0.59), "0.59"); assert_eq!(format_float(61960.0), "61960"); assert_eq!(format_float(0.5), "0.5"); assert_eq!(format_float(1.085), "1.085"); } #[test] fn as_f64_widens_int_and_rejects_string() { assert_eq!(Value::Int(5).as_f64(), Some(5.0)); assert_eq!(Value::Float(1.5).as_f64(), Some(1.5)); assert_eq!(Value::String("x".into()).as_f64(), None); } #[test] fn display_matches_format() { assert_eq!(Value::Int(580).to_string(), "580"); assert_eq!(Value::Float(0.5).to_string(), "0.5"); assert_eq!(Value::String("lifetime".into()).to_string(), "lifetime"); } }