lmn_core/threshold/
parse.rs1use serde::Deserialize;
2
3use super::error::ThresholdError;
4use super::types::{Metric, Threshold};
5
6#[derive(Deserialize)]
8struct ThresholdEnvelope {
9 thresholds: Vec<Threshold>,
10}
11
12pub fn parse_thresholds(json_or_yaml: &str) -> Result<Vec<Threshold>, ThresholdError> {
37 let envelope: ThresholdEnvelope = serde_json::from_str(json_or_yaml).or_else(|json_err| {
38 serde_norway::from_str(json_or_yaml)
39 .map_err(|_yaml_err| ThresholdError::ParseError(json_err.to_string()))
40 })?;
41
42 validate_thresholds(envelope.thresholds)
43}
44
45pub(crate) fn validate_thresholds(
46 thresholds: Vec<Threshold>,
47) -> Result<Vec<Threshold>, ThresholdError> {
48 for t in &thresholds {
49 if !t.value.is_finite() {
50 return Err(ThresholdError::ValidationError(format!(
51 "threshold value must be finite, got: {}",
52 t.value
53 )));
54 }
55
56 if t.metric == Metric::ErrorRate && !(0.0..=1.0).contains(&t.value) {
57 return Err(ThresholdError::ValidationError(format!(
58 "error_rate threshold value must be in [0.0, 1.0], got: {}",
59 t.value
60 )));
61 }
62 }
63
64 Ok(thresholds)
65}
66
67#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::threshold::types::{Metric, Operator};
73
74 #[test]
75 fn parse_valid_json_thresholds() {
76 let json = r#"{
77 "thresholds": [
78 { "metric": "latency_p99", "operator": "lt", "value": 200.0 },
79 { "metric": "error_rate", "operator": "lte", "value": 0.05 }
80 ]
81 }"#;
82 let thresholds = parse_thresholds(json).expect("should parse");
83 assert_eq!(thresholds.len(), 2);
84 assert_eq!(thresholds[0].metric, Metric::LatencyP99);
85 assert_eq!(thresholds[0].operator, Operator::Lt);
86 assert!((thresholds[0].value - 200.0).abs() < f64::EPSILON);
87 assert_eq!(thresholds[1].metric, Metric::ErrorRate);
88 assert_eq!(thresholds[1].operator, Operator::Lte);
89 }
90
91 #[test]
92 fn parse_valid_yaml_thresholds() {
93 let yaml = "thresholds:\n - metric: latency_p99\n operator: lt\n value: 200.0\n - metric: throughput_rps\n operator: gte\n value: 10.0\n";
94 let thresholds = parse_thresholds(yaml).expect("should parse YAML");
95 assert_eq!(thresholds.len(), 2);
96 assert_eq!(thresholds[0].metric, Metric::LatencyP99);
97 assert_eq!(thresholds[1].metric, Metric::ThroughputRps);
98 }
99
100 #[test]
101 fn parse_invalid_metric_returns_error() {
102 let json = r#"{ "thresholds": [{ "metric": "does_not_exist", "operator": "lt", "value": 100.0 }] }"#;
103 let result = parse_thresholds(json);
104 assert!(result.is_err());
105 }
106
107 #[test]
108 fn parse_invalid_operator_returns_error() {
109 let json = r#"{ "thresholds": [{ "metric": "latency_p99", "operator": "not_an_op", "value": 100.0 }] }"#;
110 let result = parse_thresholds(json);
111 assert!(result.is_err());
112 }
113
114 #[test]
115 fn parse_error_rate_above_1_returns_error() {
116 let json =
117 r#"{ "thresholds": [{ "metric": "error_rate", "operator": "lte", "value": 1.5 }] }"#;
118 let result = parse_thresholds(json);
119 assert!(matches!(result, Err(ThresholdError::ValidationError(_))));
120 }
121
122 #[test]
123 fn parse_infinite_value_returns_error() {
124 let yaml = "thresholds:\n - metric: latency_p99\n operator: lt\n value: .inf\n";
128 let result = parse_thresholds(yaml);
129 assert!(matches!(result, Err(ThresholdError::ValidationError(_))));
130 }
131
132 #[test]
133 fn parse_empty_thresholds_array_ok() {
134 let json = r#"{ "thresholds": [] }"#;
135 let thresholds = parse_thresholds(json).expect("empty array is valid");
136 assert!(thresholds.is_empty());
137 }
138}