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(Clone, 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![selectors::parse_verbose(
142 "single_counter_test_component:root:counter"
143 )
144 .unwrap(),],
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_invalid_config() {
157 let invalid_json = r#"{
158 "project_id": 5,
159 "poll_rate_sec": 3,
160 "invalid_field": "bad bad bad"
161 }"#;
162
163 serde_json5::from_str::<ProjectConfig>(invalid_json)
164 .expect_err("fail to load invalid config");
165 }
166
167 #[fuchsia::test]
168 fn parse_optional_args() {
169 let true_json = r#"{
170 "project_id": 5,
171 "poll_rate_sec": 60,
172 "metrics": [
173 {
174 // Test comment for json5 portability.
175 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
176 "metric_id": 1,
177 "metric_type": "Occurrence",
178 "event_codes": [0, 0],
179 "upload_once": true,
180 }
181 ]
182 }"#;
183
184 let config: ProjectConfig = serde_json5::from_str(true_json).expect("parse json");
185 assert_eq!(
186 config,
187 ProjectConfig {
188 project_id: ProjectId(5),
189 poll_rate_sec: 60,
190 customer_id: CustomerId(1),
191 metrics: vec![MetricConfig {
192 selectors: vec![
193 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
194 ],
195 metric_id: MetricId(1),
196 metric_type: MetricType::Occurrence,
197 upload_once: true,
198 event_codes: vec![EventCode(0), EventCode(0)],
199 project_id: None,
200 }]
201 }
202 );
203
204 let false_json = r#"{
205 "project_id": 5,
206 "poll_rate_sec": 60,
207 "metrics": [
208 {
209 // Test comment for json5 portability.
210 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
211 "metric_id": 1,
212 "metric_type": "Occurrence",
213 "event_codes": [0, 0],
214 "upload_once": false,
215 }
216 ]
217 }"#;
218 let config: ProjectConfig = serde_json5::from_str(false_json).expect("parse json");
219 assert_eq!(
220 config,
221 ProjectConfig {
222 project_id: ProjectId(5),
223 poll_rate_sec: 60,
224 customer_id: CustomerId(1),
225 metrics: vec![MetricConfig {
226 selectors: vec![
227 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
228 ],
229 metric_id: MetricId(1),
230 metric_type: MetricType::Occurrence,
231 upload_once: false,
232 event_codes: vec![EventCode(0), EventCode(0)],
233 project_id: None
234 }]
235 }
236 );
237 }
238 #[fuchsia::test]
239 fn default_customer_id() {
240 let default_json = r#"{
241 "project_id": 5,
242 "poll_rate_sec": 60,
243 "metrics": [
244 {
245 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
246 "metric_id": 1,
247 "metric_type": "Occurrence",
248 "event_codes": [0, 0]
249 }
250 ]
251 }"#;
252 let with_customer_id_json = r#"{
253 "customer_id": 6,
254 "project_id": 5,
255 "poll_rate_sec": 3,
256 "metrics": [
257 {
258 "selector": "single_counter_test_component:root:counter",
259 "metric_id": 1,
260 "metric_type": "Occurrence",
261 "event_codes": [0, 0]
262 }
263 ]
264 }
265 "#;
266
267 let config: ProjectConfig = serde_json5::from_str(default_json).expect("deserialize");
268 assert_eq!(
269 config,
270 ProjectConfig {
271 project_id: ProjectId(5),
272 poll_rate_sec: 60,
273 customer_id: CustomerId(1),
274 metrics: vec![MetricConfig {
275 selectors: vec![
276 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
277 ],
278 metric_id: MetricId(1),
279 metric_type: MetricType::Occurrence,
280 upload_once: false,
281 event_codes: vec![EventCode(0), EventCode(0)],
282 project_id: None,
283 }],
284 }
285 );
286
287 let config: ProjectConfig =
288 serde_json5::from_str(with_customer_id_json).expect("deserialize");
289 assert_eq!(
290 config,
291 ProjectConfig {
292 project_id: ProjectId(5),
293 poll_rate_sec: 3,
294 customer_id: CustomerId(6),
295 metrics: vec![MetricConfig {
296 selectors: vec![selectors::parse_verbose(
297 "single_counter_test_component:root:counter"
298 )
299 .unwrap(),],
300 metric_id: MetricId(1),
301 metric_type: MetricType::Occurrence,
302 upload_once: false,
303 event_codes: vec![EventCode(0), EventCode(0)],
304 project_id: None,
305 }]
306 }
307 );
308 }
309
310 #[fuchsia::test]
311 fn missing_event_codes_ok() {
312 let default_json = r#"{
313 "project_id": 5,
314 "poll_rate_sec": 60,
315 "metrics": [
316 {
317 "selector": "bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests",
318 "metric_id": 1,
319 "metric_type": "Occurrence",
320 }
321 ]
322 }"#;
323
324 let config: ProjectConfig = serde_json5::from_str(default_json).expect("deserialize");
325 assert_eq!(
326 config,
327 ProjectConfig {
328 project_id: ProjectId(5),
329 poll_rate_sec: 60,
330 customer_id: CustomerId(1),
331 metrics: vec![MetricConfig {
332 selectors: vec![
333 selectors::parse_verbose("bootstrap/archivist:root/all_archive_accessor:inspect_batch_iterator_get_next_requests").unwrap(),
334 ],
335 metric_id: MetricId(1),
336 metric_type: MetricType::Occurrence,
337 upload_once: false,
338 event_codes: vec![],
339 project_id: None,
340 }]
341 }
342 );
343 }
344}