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}