1use crate::common::{EventCode, MetricId, MetricType, ProjectId};
12use fidl_fuchsia_diagnostics_common::Selector;
13use serde::{Deserialize, Deserializer, de};
14use std::fmt;
15
16#[derive(Debug, PartialEq)]
19pub enum EventCodeSpec {
20 Code(EventCode),
22 Name(String),
24}
25
26impl<'de> Deserialize<'de> for EventCodeSpec {
27 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
28 where
29 D: Deserializer<'de>,
30 {
31 deserializer.deserialize_any(EventCodeSpecVisitor)
32 }
33}
34
35struct EventCodeSpecVisitor;
36
37impl<'de> de::Visitor<'de> for EventCodeSpecVisitor {
38 type Value = EventCodeSpec;
39
40 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.write_str("a u32 event code or an event code name string")
42 }
43
44 fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
45 u32::try_from(value)
46 .map(|code| EventCodeSpec::Code(EventCode(code)))
47 .map_err(|_| E::custom(format!("event code {value} does not fit in a u32")))
48 }
49
50 fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
51 u32::try_from(value)
52 .map(|code| EventCodeSpec::Code(EventCode(code)))
53 .map_err(|_| E::custom(format!("event code {value} does not fit in a u32")))
54 }
55
56 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
57 Ok(EventCodeSpec::Name(value.to_owned()))
58 }
59}
60
61#[derive(Deserialize, Debug, PartialEq)]
63pub struct ProjectConfig {
64 pub project_id: ProjectId,
66
67 pub data_sets: Vec<DataSetConfig>,
69}
70
71#[derive(Deserialize, Debug, PartialEq)]
73pub struct DataSetConfig {
74 #[serde(deserialize_with = "crate::utils::greater_than_zero")]
76 pub poll_rate_sec: i64,
77
78 pub metrics: Vec<MetricConfig>,
80}
81
82#[derive(Deserialize, Debug, PartialEq)]
86pub struct MetricConfig {
87 #[serde(rename = "selector", deserialize_with = "crate::utils::one_or_many_selectors")]
89 pub selectors: Vec<Selector>,
90
91 #[serde(default)]
93 pub metric_id: Option<MetricId>,
94
95 #[serde(default)]
97 pub metric_name: Option<String>,
98
99 pub metric_type: MetricType,
101
102 #[serde(default)]
106 pub event_codes: Vec<EventCodeSpec>,
107
108 #[serde(default)]
111 pub upload_once: bool,
112}
113
114#[derive(Deserialize, Debug, PartialEq)]
117pub struct ProjectTemplate {
118 pub project_id: ProjectId,
120
121 #[serde(deserialize_with = "crate::utils::greater_than_zero")]
123 pub poll_rate_sec: i64,
124
125 pub metrics: Vec<MetricTemplate>,
127}
128
129#[derive(Deserialize, Debug, PartialEq)]
133pub struct MetricTemplate {
134 #[serde(rename = "selector", deserialize_with = "crate::utils::one_or_many_strings")]
136 pub selectors: Vec<String>,
137
138 #[serde(default)]
140 pub metric_id: Option<MetricId>,
141
142 #[serde(default)]
144 pub metric_name: Option<String>,
145
146 pub metric_type: MetricType,
148
149 #[serde(default)]
152 pub event_codes: Vec<EventCodeSpec>,
153
154 #[serde(default)]
157 pub upload_once: bool,
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[fuchsia::test]
165 fn parse_project_config_with_metric_name() {
166 let json = r#"{
167 project_id: 10,
168 data_sets: [{
169 poll_rate_sec: 60,
170 metrics: [{
171 selector: "core/foo:root:bar",
172 metric_name: "test_occurrence",
173 metric_type: "Occurrence",
174 event_codes: [1],
175 }],
176 }],
177 }"#;
178 let config: ProjectConfig = serde_json5::from_str(json).expect("parse json");
179 assert_eq!(
180 config,
181 ProjectConfig {
182 project_id: ProjectId(10),
183 data_sets: vec![DataSetConfig {
184 poll_rate_sec: 60,
185 metrics: vec![MetricConfig {
186 selectors: vec![selectors::parse_verbose("core/foo:root:bar").unwrap()],
187 metric_id: None,
188 metric_name: Some("test_occurrence".into()),
189 metric_type: MetricType::Occurrence,
190 event_codes: vec![EventCodeSpec::Code(EventCode(1))],
191 upload_once: false,
192 }],
193 }],
194 }
195 );
196 }
197
198 #[fuchsia::test]
199 fn parse_project_template_with_metric_name() {
200 let json = r#"{
201 project_id: 10,
202 poll_rate_sec: 60,
203 metrics: [{
204 selector: "{MONIKER}:root:val",
205 metric_name: "test_fire_histogram",
206 metric_type: "IntHistogram",
207 event_codes: [2],
208 }],
209 }"#;
210 let template: ProjectTemplate = serde_json5::from_str(json).expect("parse json");
211 assert_eq!(
212 template,
213 ProjectTemplate {
214 project_id: ProjectId(10),
215 poll_rate_sec: 60,
216 metrics: vec![MetricTemplate {
217 selectors: vec!["{MONIKER}:root:val".into()],
218 metric_id: None,
219 metric_name: Some("test_fire_histogram".into()),
220 metric_type: MetricType::IntHistogram,
221 event_codes: vec![EventCodeSpec::Code(EventCode(2))],
222 upload_once: false,
223 }],
224 }
225 );
226 }
227
228 #[fuchsia::test]
229 fn parse_event_codes_with_names() {
230 let json = r#"{
231 selector: "{MONIKER}:root:val",
232 metric_name: "test_fire_histogram",
233 metric_type: "IntHistogram",
234 event_codes: [1, "Failed", "3"],
235 }"#;
236 let template: MetricTemplate = serde_json5::from_str(json).expect("parse json");
237 assert_eq!(
238 template.event_codes,
239 vec![
240 EventCodeSpec::Code(EventCode(1)),
241 EventCodeSpec::Name("Failed".into()),
242 EventCodeSpec::Name("3".into()),
243 ]
244 );
245 }
246
247 #[fuchsia::test]
248 fn parse_invalid_event_codes() {
249 for (event_code, expected_error) in [
250 ("-1", "event code -1 does not fit in a u32"),
251 ("4294967296", "event code 4294967296 does not fit in a u32"),
252 ("3.5", "expected a u32 event code or an event code name string"),
253 ("true", "expected a u32 event code or an event code name string"),
254 ] {
255 let json = format!(
256 r#"{{
257 selector: "{{MONIKER}}:root:val",
258 metric_name: "test_fire_histogram",
259 metric_type: "IntHistogram",
260 event_codes: [{event_code}],
261 }}"#
262 );
263 let err = serde_json5::from_str::<MetricTemplate>(&json).unwrap_err();
264 assert!(
265 err.to_string().contains(expected_error),
266 "event code {event_code}: unexpected error: {err}"
267 );
268 }
269 }
270}