sdl_parser/
metric.rs

1use std::collections::HashMap;
2
3use anyhow::{anyhow, Result};
4use serde::{Deserialize, Serialize};
5
6use crate::{condition::Condition, helpers::Connection, Formalize};
7
8#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
9pub enum MetricType {
10    #[serde(alias = "manual", alias = "MANUAL")]
11    Manual,
12    #[serde(alias = "conditional", alias = "CONDITIONAL")]
13    Conditional,
14}
15
16#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
17pub struct Metric {
18    #[serde(default, alias = "Name", alias = "NAME")]
19    pub name: Option<String>,
20    #[serde(rename = "type", alias = "Type", alias = "TYPE")]
21    pub metric_type: MetricType,
22    #[serde(alias = "Artifact", alias = "ARTIFACT")]
23    pub artifact: Option<bool>,
24    #[serde(alias = "max-score", alias = "MAX-SCORE")]
25    pub max_score: u32,
26    #[serde(alias = "condition", alias = "CONDITION")]
27    pub condition: Option<String>,
28    #[serde(alias = "Description", alias = "DESCRIPTION")]
29    pub description: Option<String>,
30}
31
32pub type Metrics = HashMap<String, Metric>;
33
34impl Formalize for Metric {
35    fn formalize(&mut self) -> Result<()> {
36        if self.max_score == 0 {
37            return Err(anyhow!("Metric `max-score` can not be 0"));
38        }
39        match self.metric_type {
40            MetricType::Manual => {
41                if self.condition.is_some() {
42                    return Err(anyhow!("Manual Metric can not have a Condition"));
43                }
44            }
45            MetricType::Conditional => {
46                if self.condition.is_none() {
47                    return Err(anyhow!("Conditional Metric must have a Condition"));
48                }
49                if self.artifact.is_some() {
50                    return Err(anyhow!("Conditional Metric can not have an Artifact"));
51                }
52            }
53        }
54        Ok(())
55    }
56}
57
58impl Connection<Condition> for (&String, &Metric) {
59    fn validate_connections(&self, potential_condition_names: &Option<Vec<String>>) -> Result<()> {
60        if let Some(condition_names) = potential_condition_names {
61            if let Some(condition_name) = &self.1.condition {
62                if !condition_names.contains(condition_name) {
63                    return Err(anyhow::anyhow!(
64                        "Condition \"{condition_name}\" not found under Scenario Conditions"
65                    ));
66                }
67            }
68        } else if let Some(condition_name) = &self.1.condition {
69            return Err(anyhow::anyhow!(
70                "Condition \"{condition_name}\" not found under Scenario",
71            ));
72        }
73        Ok(())
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::parse_sdl;
81
82    #[test]
83    fn parses_sdl_with_metrics() {
84        let sdl = r#"
85            name: test-scenario
86            description: some-description
87            metrics:
88                metric-1:
89                    type: MANUAL
90                    artifact: true
91                    max-score: 10
92                metric-2:
93                    type: CONDITIONAL
94                    max-score: 10
95                    condition: condition-1
96            conditions:
97                condition-1:
98                    command: executable/path.sh
99                    interval: 30
100        "#;
101        let metrics = parse_sdl(sdl).unwrap().metrics;
102        insta::with_settings!({sort_maps => true}, {
103                insta::assert_yaml_snapshot!(metrics);
104        });
105    }
106
107    #[test]
108    fn parses_manual_metric() {
109        let metric_string = r#"
110          type: MANUAL
111          artifact: true
112          max-score: 10
113        "#;
114        let mut metric: Metric = serde_yaml::from_str(metric_string).unwrap();
115        assert!(metric.formalize().is_ok());
116    }
117
118    #[test]
119    fn fails_manual_metric_with_condition() {
120        let metric_string = r#"
121          type: MANUAL
122          artifact: true
123          max-score: 10
124          condition: some-condition
125        "#;
126        let mut metric: Metric = serde_yaml::from_str(metric_string).unwrap();
127        assert!(metric.formalize().is_err());
128    }
129
130    #[test]
131    fn parses_conditional_metric() {
132        let metric_string = r#"
133          type: CONDITIONAL
134          max-score: 10
135          condition: some-condition
136        "#;
137        let mut metric: Metric = serde_yaml::from_str(metric_string).unwrap();
138        assert!(metric.formalize().is_ok());
139    }
140
141    #[test]
142    fn fails_conditional_metric_with_artifact() {
143        let metric_string = r#"
144          type: CONDITIONAL
145          artifact: true
146          max-score: 10
147          condition: some-condition
148        "#;
149        let mut metric: Metric = serde_yaml::from_str(metric_string).unwrap();
150        assert!(metric.formalize().is_err());
151    }
152
153    #[test]
154    fn fails_conditional_metric_without_artifact() {
155        let metric_string = r#"
156          type: CONDITIONAL
157          max-score: 10
158        "#;
159        let mut metric: Metric = serde_yaml::from_str(metric_string).unwrap();
160        assert!(metric.formalize().is_err());
161    }
162}