1use crate::common::{CustomerId, EventCode, MetricId, MetricType, ProjectId};
6use fidl_fuchsia_diagnostics::Selector;
7use selectors::SelectorDisplayOptions;
8use serde::ser::{Error, SerializeSeq};
9use serde::{Deserialize, Serialize, Serializer};
10
11#[derive(Serialize, Deserialize, Debug, PartialEq)]
13pub struct ProjectConfig {
14 pub project_id: ProjectId,
16
17 #[serde(default)]
20 pub customer_id: CustomerId,
21
22 #[serde(deserialize_with = "crate::utils::greater_than_zero")]
24 pub poll_rate_sec: i64,
25
26 pub metrics: Vec<MetricConfig>,
28}
29
30#[derive(Serialize, Deserialize, Debug, PartialEq)]
32pub struct MetricConfig {
33 #[serde(
35 rename = "selector",
36 deserialize_with = "crate::utils::one_or_many_selectors",
37 serialize_with = "selectors_to_string"
38 )]
39 pub selectors: Vec<Selector>,
40
41 pub metric_id: MetricId,
43
44 pub metric_type: MetricType,
46
47 #[serde(default)]
51 pub event_codes: Vec<EventCode>,
52
53 #[serde(default)]
56 pub upload_once: bool,
57
58 pub project_id: Option<ProjectId>,
62}
63
64pub fn selectors_to_string<S>(selectors: &[Selector], serializer: S) -> Result<S::Ok, S::Error>
65where
66 S: Serializer,
67{
68 let mut seq = serializer.serialize_seq(Some(selectors.len()))?;
69 for selector in selectors {
70 let selector_string =
71 selectors::selector_to_string(selector, SelectorDisplayOptions::never_wrap_in_quotes())
72 .map_err(S::Error::custom)?;
73 seq.serialize_element(&selector_string)?;
74 }
75 seq.end()
76}
77
78#[cfg(test)]
79mod test {
80 use super::*;
81
82 #[fuchsia::test]
83 fn parse_valid_sampler_config() {
84 let ok_json = r#"{
85 "project_id": 5,
86 "poll_rate_sec": 60,
87 "metrics": [
88 {
89 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
90 "metric_id": 1,
91 "metric_type": "Occurrence",
92 "event_codes": [0, 0]
93 }
94 ]
95 }"#;
96
97 let config: ProjectConfig = serde_json5::from_str(ok_json).expect("parse json");
98 assert_eq!(config, ProjectConfig {
99 project_id: ProjectId(5),
100 poll_rate_sec: 60,
101 customer_id: CustomerId(1),
102 metrics: vec![
103 MetricConfig {
104 selectors: vec![
105 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
106 ],
107 metric_id: MetricId(1),
108 metric_type: MetricType::Occurrence,
109 event_codes: vec![EventCode(0), EventCode(0)],
110 project_id: None,
111 upload_once: false,
112 }
113 ]
114 });
115 }
116
117 #[fuchsia::test]
118 fn parse_valid_sampler_config_json5() {
119 let ok_json = r#"{
120 project_id: 5,
121 poll_rate_sec: 3,
122 metrics: [
123 {
124 // Test comment for json5 portability.
125 selector: "single_counter_test_component:root:counter",
126 metric_id: 1,
127 metric_type: "Occurrence",
128 event_codes: [0, 0]
129 }
130 ]
131 }"#;
132
133 let config: ProjectConfig = serde_json5::from_str(ok_json).expect("parse json");
134 assert_eq!(
135 config,
136 ProjectConfig {
137 project_id: ProjectId(5),
138 poll_rate_sec: 3,
139 customer_id: CustomerId(1),
140 metrics: vec![MetricConfig {
141 selectors: vec![
142 selectors::parse_verbose("single_counter_test_component:root:counter")
143 .unwrap(),
144 ],
145 metric_id: MetricId(1),
146 metric_type: MetricType::Occurrence,
147 upload_once: false,
148 event_codes: vec![EventCode(0), EventCode(0)],
149 project_id: None,
150 }]
151 }
152 );
153 }
154
155 #[fuchsia::test]
156 fn parse_valid_sampler_config_multiple_selectors() {
157 let ok_json = r#"{
158 project_id: 5,
159 poll_rate_sec: 3,
160 metrics: [
161 {
162 selector: ["component:root:one", "component:root:two"],
163 metric_id: 1,
164 metric_type: "Occurrence",
165 event_codes: [0, 0]
166 }
167 ]
168 }"#;
169
170 let config: ProjectConfig = serde_json5::from_str(ok_json).expect("parse json");
171 assert_eq!(
172 config,
173 ProjectConfig {
174 project_id: ProjectId(5),
175 poll_rate_sec: 3,
176 customer_id: CustomerId(1),
177 metrics: vec![MetricConfig {
178 selectors: vec![
179 selectors::parse_verbose("component:root:one").unwrap(),
180 selectors::parse_verbose("component:root:two").unwrap(),
181 ],
182 metric_id: MetricId(1),
183 metric_type: MetricType::Occurrence,
184 upload_once: false,
185 event_codes: vec![EventCode(0), EventCode(0)],
186 project_id: None,
187 }]
188 }
189 );
190 }
191
192 #[fuchsia::test]
193 fn parse_invalid_config() {
194 let invalid_json = r#"{
195 "project_id": 5,
196 "poll_rate_sec": 3,
197 "invalid_field": "bad bad bad"
198 }"#;
199
200 serde_json5::from_str::<ProjectConfig>(invalid_json)
201 .expect_err("fail to load invalid config");
202 }
203
204 #[fuchsia::test]
205 fn parse_optional_args() {
206 let true_json = r#"{
207 "project_id": 5,
208 "poll_rate_sec": 60,
209 "metrics": [
210 {
211 // Test comment for json5 portability.
212 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
213 "metric_id": 1,
214 "metric_type": "Occurrence",
215 "event_codes": [0, 0],
216 "upload_once": true,
217 }
218 ]
219 }"#;
220
221 let config: ProjectConfig = serde_json5::from_str(true_json).expect("parse json");
222 assert_eq!(
223 config,
224 ProjectConfig {
225 project_id: ProjectId(5),
226 poll_rate_sec: 60,
227 customer_id: CustomerId(1),
228 metrics: vec![MetricConfig {
229 selectors: vec![
230 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
231 ],
232 metric_id: MetricId(1),
233 metric_type: MetricType::Occurrence,
234 upload_once: true,
235 event_codes: vec![EventCode(0), EventCode(0)],
236 project_id: None,
237 }]
238 }
239 );
240
241 let false_json = r#"{
242 "project_id": 5,
243 "poll_rate_sec": 60,
244 "metrics": [
245 {
246 // Test comment for json5 portability.
247 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
248 "metric_id": 1,
249 "metric_type": "Occurrence",
250 "event_codes": [0, 0],
251 "upload_once": false,
252 }
253 ]
254 }"#;
255 let config: ProjectConfig = serde_json5::from_str(false_json).expect("parse json");
256 assert_eq!(
257 config,
258 ProjectConfig {
259 project_id: ProjectId(5),
260 poll_rate_sec: 60,
261 customer_id: CustomerId(1),
262 metrics: vec![MetricConfig {
263 selectors: vec![
264 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
265 ],
266 metric_id: MetricId(1),
267 metric_type: MetricType::Occurrence,
268 upload_once: false,
269 event_codes: vec![EventCode(0), EventCode(0)],
270 project_id: None
271 }]
272 }
273 );
274 }
275 #[fuchsia::test]
276 fn default_customer_id() {
277 let default_json = r#"{
278 "project_id": 5,
279 "poll_rate_sec": 60,
280 "metrics": [
281 {
282 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
283 "metric_id": 1,
284 "metric_type": "Occurrence",
285 "event_codes": [0, 0]
286 }
287 ]
288 }"#;
289 let with_customer_id_json = r#"{
290 "customer_id": 6,
291 "project_id": 5,
292 "poll_rate_sec": 3,
293 "metrics": [
294 {
295 "selector": "single_counter_test_component:root:counter",
296 "metric_id": 1,
297 "metric_type": "Occurrence",
298 "event_codes": [0, 0]
299 }
300 ]
301 }
302 "#;
303
304 let config: ProjectConfig = serde_json5::from_str(default_json).expect("deserialize");
305 assert_eq!(
306 config,
307 ProjectConfig {
308 project_id: ProjectId(5),
309 poll_rate_sec: 60,
310 customer_id: CustomerId(1),
311 metrics: vec![MetricConfig {
312 selectors: vec![
313 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
314 ],
315 metric_id: MetricId(1),
316 metric_type: MetricType::Occurrence,
317 upload_once: false,
318 event_codes: vec![EventCode(0), EventCode(0)],
319 project_id: None,
320 }],
321 }
322 );
323
324 let config: ProjectConfig =
325 serde_json5::from_str(with_customer_id_json).expect("deserialize");
326 assert_eq!(
327 config,
328 ProjectConfig {
329 project_id: ProjectId(5),
330 poll_rate_sec: 3,
331 customer_id: CustomerId(6),
332 metrics: vec![MetricConfig {
333 selectors: vec![
334 selectors::parse_verbose("single_counter_test_component:root:counter")
335 .unwrap(),
336 ],
337 metric_id: MetricId(1),
338 metric_type: MetricType::Occurrence,
339 upload_once: false,
340 event_codes: vec![EventCode(0), EventCode(0)],
341 project_id: None,
342 }]
343 }
344 );
345 }
346
347 #[fuchsia::test]
348 fn missing_event_codes_ok() {
349 let default_json = r#"{
350 "project_id": 5,
351 "poll_rate_sec": 60,
352 "metrics": [
353 {
354 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
355 "metric_id": 1,
356 "metric_type": "Occurrence",
357 }
358 ]
359 }"#;
360
361 let config: ProjectConfig = serde_json5::from_str(default_json).expect("deserialize");
362 assert_eq!(
363 config,
364 ProjectConfig {
365 project_id: ProjectId(5),
366 poll_rate_sec: 60,
367 customer_id: CustomerId(1),
368 metrics: vec![MetricConfig {
369 selectors: vec![
370 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
371 ],
372 metric_id: MetricId(1),
373 metric_type: MetricType::Occurrence,
374 upload_once: false,
375 event_codes: vec![],
376 project_id: None,
377 }]
378 }
379 );
380 }
381}