use crate::timestamp::Timestamp; use serde::Serialize; use std::cmp::Ordering; use std::fmt; /// A simple, self-describing value used by metrics/labels without pulling in JSON. #[allow(dead_code)] #[derive(Debug, Clone, Serialize)] pub enum MetricPrimitive { String(String), Number(Number), Timestamp(Timestamp), /// Milliseconds elapsed; distinct from `Number` so it reads unambiguously in JSON/label form. Duration(i64), } impl PartialEq for MetricPrimitive { fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for MetricPrimitive {} impl PartialOrd for MetricPrimitive { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for MetricPrimitive { fn cmp(&self, other: &Self) -> Ordering { fn rank(v: &MetricPrimitive) -> u8 { match v { MetricPrimitive::String(_) => 0, MetricPrimitive::Number(_) => 1, MetricPrimitive::Timestamp(_) => 2, MetricPrimitive::Duration(_) => 3, } } match (self, other) { (MetricPrimitive::String(a), MetricPrimitive::String(b)) => a.cmp(b), (MetricPrimitive::Number(a), MetricPrimitive::Number(b)) => a.cmp(b), (MetricPrimitive::Timestamp(a), MetricPrimitive::Timestamp(b)) => a.cmp(b), (MetricPrimitive::Duration(a), MetricPrimitive::Duration(b)) => a.cmp(b), (a, b) => rank(a).cmp(&rank(b)), } } } /// Textual form used to compare label filters (query-string values) against a formatted `MetricPrimitive` rather than parsing them into one. impl fmt::Display for MetricPrimitive { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MetricPrimitive::String(s) => write!(f, "{s}"), MetricPrimitive::Number(Number::Int(n)) => write!(f, "{n}"), MetricPrimitive::Number(Number::Float(n)) => write!(f, "{n}"), MetricPrimitive::Timestamp(ts) => write!(f, "{}", ts.as_millis()), MetricPrimitive::Duration(ms) => write!(f, "{ms}"), } } } /// Numbers are kept as a tagged int/float pair so exact integers stay exact. #[allow(dead_code)] #[derive(Debug, Clone, Copy, Serialize)] pub enum Number { Int(i64), Float(f64), } impl Number { /// Widens to `f64` for cross-kind numeric comparisons; `pub(crate)` since the filter evaluator in `engine.rs` needs it. pub(crate) fn as_f64(&self) -> f64 { match self { Number::Int(v) => *v as f64, Number::Float(v) => *v, } } } impl PartialEq for Number { fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for Number {} impl PartialOrd for Number { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Number { fn cmp(&self, other: &Self) -> Ordering { if let (Number::Int(a), Number::Int(b)) = (self, other) { return a.cmp(b); } self.as_f64().total_cmp(&other.as_f64()) } }