1use crate::common::{EventCode, MetricId, MetricType, ProjectId};
6use crate::utils::OneOrMany;
7use anyhow::bail;
8use component_id_index::InstanceId;
9use moniker::ExtendedMoniker;
10use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
11use std::marker::PhantomData;
12use std::str::FromStr;
13
14pub use crate::runtime::{DataSetConfig, MetricConfig, ProjectConfig};
15
16#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
21pub struct ProjectTemplate {
22 pub project_id: ProjectId,
24
25 #[serde(deserialize_with = "crate::utils::greater_than_zero")]
27 pub poll_rate_sec: i64,
28
29 pub metrics: Vec<MetricTemplate>,
31}
32
33impl ProjectTemplate {
34 pub fn expand(self, components: &ComponentIdInfoList) -> Result<ProjectConfig, anyhow::Error> {
35 let ProjectTemplate { project_id, poll_rate_sec, metrics } = self;
36 let mut metric_configs = Vec::with_capacity(metrics.len() * components.len());
37 for component in components.iter() {
38 for metric in metrics.iter() {
39 if let Some(metric_config) = metric.expand(component)? {
40 metric_configs.push(metric_config);
41 }
42 }
43 }
44
45 let data_sets = vec![DataSetConfig { poll_rate_sec, metrics: metric_configs }];
46
47 Ok(ProjectConfig { project_id, data_sets })
48 }
49}
50
51#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
54pub struct MetricTemplate {
55 #[serde(rename = "selector", deserialize_with = "one_or_many_strings")]
58 pub selectors: Vec<String>,
59
60 pub metric_id: MetricId,
62
63 pub metric_type: MetricType,
65
66 #[serde(default)]
75 pub event_codes: Vec<EventCode>,
76
77 #[serde(default)]
80 pub upload_once: bool,
81}
82
83#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
84pub struct ComponentIdInfo {
85 #[serde(deserialize_with = "moniker_deserialize", serialize_with = "moniker_serialize")]
87 pub moniker: ExtendedMoniker,
88
89 #[serde(
91 default,
92 deserialize_with = "instance_id_deserialize",
93 serialize_with = "instance_id_serialize"
94 )]
95 pub instance_id: Option<InstanceId>,
96
97 #[serde(alias = "id")]
99 pub event_id: EventCode,
100
101 pub label: String,
103}
104
105#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)]
106pub struct ComponentIdInfoList(Vec<ComponentIdInfo>);
107
108impl ComponentIdInfoList {
109 pub fn new(infos: Vec<ComponentIdInfo>) -> Self {
110 Self(infos)
111 }
112
113 pub fn add_instance_ids(&mut self, index: &component_id_index::Index) {
114 for component in self.0.iter_mut() {
115 if let ExtendedMoniker::ComponentInstance(moniker) = &component.moniker {
116 component.instance_id = index.id_for_moniker(moniker).cloned();
117 }
118 }
119 }
120
121 pub fn iter(&self) -> impl Iterator<Item = &ComponentIdInfo> {
122 self.0.iter()
123 }
124}
125
126impl std::ops::Deref for ComponentIdInfoList {
127 type Target = Vec<ComponentIdInfo>;
128
129 fn deref(&self) -> &Self::Target {
130 &self.0
131 }
132}
133
134impl std::ops::DerefMut for ComponentIdInfoList {
135 fn deref_mut(&mut self) -> &mut Self::Target {
136 &mut self.0
137 }
138}
139
140impl IntoIterator for ComponentIdInfoList {
141 type Item = ComponentIdInfo;
142 type IntoIter = std::vec::IntoIter<Self::Item>;
143
144 fn into_iter(self) -> Self::IntoIter {
145 self.0.into_iter()
146 }
147}
148
149impl MetricTemplate {
150 fn expand(&self, component: &ComponentIdInfo) -> Result<Option<MetricConfig>, anyhow::Error> {
151 let MetricTemplate { selectors, metric_id, metric_type, event_codes, upload_once } = self;
152 let mut result_selectors = Vec::with_capacity(selectors.len());
153 for selector in selectors {
154 if let Some(selector_string) = interpolate_template(selector, component)? {
155 let selector = crate::utils::parse_selector(&selector_string)?;
156 result_selectors.push(selector);
157 }
158 }
159 if result_selectors.is_empty() {
160 return Ok(None);
161 }
162 let mut event_codes = event_codes.to_vec();
163 event_codes.insert(0, component.event_id);
164 Ok(Some(MetricConfig {
165 event_codes,
166 selectors: result_selectors,
167 metric_id: *metric_id,
168 metric_type: *metric_type,
169 upload_once: *upload_once,
170 }))
171 }
172}
173
174const MONIKER_INTERPOLATION: &str = "{MONIKER}";
175const INSTANCE_ID_INTERPOLATION: &str = "{INSTANCE_ID}";
176
177fn interpolate_template(
180 template: &str,
181 component_info: &ComponentIdInfo,
182) -> Result<Option<String>, anyhow::Error> {
183 let moniker_position = template.find(MONIKER_INTERPOLATION);
184 let instance_id_position = template.find(INSTANCE_ID_INTERPOLATION);
185 let separator_position = template.find(":");
186 match (moniker_position, separator_position, instance_id_position, &component_info.instance_id)
192 {
193 (Some(i), Some(s), _, _) if i < s => {
194 Ok(Some(template.replace(MONIKER_INTERPOLATION, component_info.moniker.as_ref())))
195 }
196 (Some(_), Some(_), _, _) => Ok(Some(template.replace(
197 MONIKER_INTERPOLATION,
198 &selectors::sanitize_string_for_selectors(component_info.moniker.as_ref()),
199 ))),
200 (_, _, Some(_), Some(id)) => {
201 Ok(Some(template.replace(INSTANCE_ID_INTERPOLATION, &id.to_string())))
202 }
203 (_, _, Some(_), None) => Ok(None),
204 (None, _, None, _) => {
205 bail!(
206 "{} and {} not found in selector template {}",
207 MONIKER_INTERPOLATION,
208 INSTANCE_ID_INTERPOLATION,
209 template
210 )
211 }
212 (Some(_), None, _, _) => {
213 bail!("Separator ':' not found in selector template {}", template)
214 }
215 }
216}
217
218fn moniker_deserialize<'de, D>(deserializer: D) -> Result<ExtendedMoniker, D::Error>
219where
220 D: Deserializer<'de>,
221{
222 let moniker_str = String::deserialize(deserializer)?;
223 ExtendedMoniker::parse_str(&moniker_str).map_err(de::Error::custom)
224}
225
226fn instance_id_deserialize<'de, D>(deserializer: D) -> Result<Option<InstanceId>, D::Error>
227where
228 D: Deserializer<'de>,
229{
230 match Option::<String>::deserialize(deserializer)? {
231 None => Ok(None),
232 Some(instance_id) => {
233 let instance_id = InstanceId::from_str(&instance_id).map_err(de::Error::custom)?;
234 Ok(Some(instance_id))
235 }
236 }
237}
238
239pub fn moniker_serialize<S>(moniker: &ExtendedMoniker, serializer: S) -> Result<S::Ok, S::Error>
240where
241 S: Serializer,
242{
243 serializer.serialize_str(moniker.as_ref())
244}
245
246pub fn instance_id_serialize<S>(
247 instance_id: &Option<InstanceId>,
248 serializer: S,
249) -> Result<S::Ok, S::Error>
250where
251 S: Serializer,
252{
253 match instance_id.as_ref() {
254 Some(instance_id) => serializer.serialize_some(&instance_id.to_string()),
255 None => serializer.serialize_none(),
256 }
257}
258
259fn one_or_many_strings<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
260where
261 D: Deserializer<'de>,
262{
263 deserializer.deserialize_any(OneOrMany(PhantomData::<String>))
264}
265
266#[derive(Serialize, Deserialize, Default)]
267pub struct MergedSamplerConfig {
268 pub fire_project_templates: Vec<ProjectTemplate>,
269 pub fire_component_configs: Vec<ComponentIdInfoList>,
270 pub project_configs: Vec<ProjectConfig>,
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use component_id_index::IndexEntry;
277 use moniker::Moniker;
278 use selectors::FastError;
279 use std::str::FromStr;
280
281 #[fuchsia::test]
282 fn parse_project_template() {
283 let template = r#"{
284 "project_id": 13,
285 "poll_rate_sec": 60,
286 "metrics": [
287 {
288 "selector": [
289 "{MONIKER}:root/path2:leaf2",
290 "foo/bar:root/{MONIKER}:leaf3",
291 "asdf/qwer:root/path4:pre-{MONIKER}-post",
292 ],
293 "metric_id": 2,
294 "metric_type": "Occurrence",
295 }
296 ]
297 }"#;
298 let config: ProjectTemplate = serde_json5::from_str(template).expect("deserialize");
299 assert_eq!(
300 config,
301 ProjectTemplate {
302 project_id: ProjectId(13),
303 poll_rate_sec: 60,
304 metrics: vec![MetricTemplate {
305 selectors: vec![
306 "{MONIKER}:root/path2:leaf2".into(),
307 "foo/bar:root/{MONIKER}:leaf3".into(),
308 "asdf/qwer:root/path4:pre-{MONIKER}-post".into(),
309 ],
310 event_codes: vec![],
311 upload_once: false,
312 metric_id: MetricId(2),
313 metric_type: MetricType::Occurrence,
314 }],
315 }
316 );
317 }
318
319 #[fuchsia::test]
320 fn parse_component_config() {
321 let components_json = r#"[
322 {
323 "id": 42,
324 "label": "Foo_42",
325 "moniker": "core/foo42"
326 },
327 {
328 "id": 10,
329 "label": "Hello",
330 "instance_id": "8775ff0afe12ca578135014a5d36a7733b0f9982bcb62a888b007cb2c31a7046",
331 "moniker": "bootstrap/hello"
332 }
333 ]"#;
334 let config: ComponentIdInfoList =
335 serde_json5::from_str(components_json).expect("deserialize");
336 assert_eq!(
337 config,
338 ComponentIdInfoList(vec![
339 ComponentIdInfo {
340 moniker: ExtendedMoniker::parse_str("core/foo42").unwrap(),
341 instance_id: None,
342 event_id: EventCode(42),
343 label: "Foo_42".into()
344 },
345 ComponentIdInfo {
346 moniker: ExtendedMoniker::parse_str("bootstrap/hello").unwrap(),
347 instance_id: Some(
348 InstanceId::from_str(
349 "8775ff0afe12ca578135014a5d36a7733b0f9982bcb62a888b007cb2c31a7046"
350 )
351 .unwrap()
352 ),
353 event_id: EventCode(10),
354 label: "Hello".into()
355 },
356 ])
357 );
358 }
359
360 #[fuchsia::test]
361 fn template_expansion_basic() {
362 let project_template = ProjectTemplate {
363 project_id: ProjectId(13),
364 poll_rate_sec: 60,
365 metrics: vec![MetricTemplate {
366 selectors: vec!["{MONIKER}:root/path:leaf".into()],
367 metric_id: MetricId(1),
368 metric_type: MetricType::Occurrence,
369 event_codes: vec![EventCode(1), EventCode(2)],
370 upload_once: true,
371 }],
372 };
373 let component_info = ComponentIdInfoList(vec![
374 ComponentIdInfo {
375 moniker: ExtendedMoniker::parse_str("core/foo42").unwrap(),
376 instance_id: None,
377 event_id: EventCode(42),
378 label: "Foo_42".into(),
379 },
380 ComponentIdInfo {
381 moniker: ExtendedMoniker::parse_str("bootstrap/hello").unwrap(),
382 instance_id: Some(
383 InstanceId::from_str(
384 "8775ff0afe12ca578135014a5d36a7733b0f9982bcb62a888b007cb2c31a7046",
385 )
386 .unwrap(),
387 ),
388 event_id: EventCode(43),
389 label: "Hello".into(),
390 },
391 ]);
392 let config = project_template.expand(&component_info).expect("expanded template");
393 assert_eq!(
394 config,
395 ProjectConfig {
396 project_id: ProjectId(13),
397 data_sets: vec![DataSetConfig {
398 poll_rate_sec: 60,
399 metrics: vec![
400 MetricConfig {
401 selectors: vec![
402 selectors::parse_selector::<FastError>("core/foo42:root/path:leaf")
403 .unwrap()
404 ],
405 metric_id: MetricId(1),
406 metric_type: MetricType::Occurrence,
407 event_codes: vec![EventCode(42), EventCode(1), EventCode(2)],
408 upload_once: true,
409 },
410 MetricConfig {
411 selectors: vec![
412 selectors::parse_selector::<FastError>(
413 "bootstrap/hello:root/path:leaf"
414 )
415 .unwrap()
416 ],
417 metric_id: MetricId(1),
418 metric_type: MetricType::Occurrence,
419 event_codes: vec![EventCode(43), EventCode(1), EventCode(2)],
420 upload_once: true,
421 },
422 ],
423 }],
424 }
425 );
426 }
427
428 #[fuchsia::test]
429 fn template_expansion_many_selectors() {
430 let project_template = ProjectTemplate {
431 project_id: ProjectId(13),
432 poll_rate_sec: 60,
433 metrics: vec![MetricTemplate {
434 selectors: vec![
435 "{MONIKER}:root/path2:leaf2".into(),
436 "foo/bar:root/{MONIKER}:leaf3".into(),
437 "asdf/qwer:root/path4:pre-{MONIKER}-post".into(),
438 ],
439 metric_id: MetricId(2),
440 metric_type: MetricType::Occurrence,
441 event_codes: vec![],
442 upload_once: false,
443 }],
444 };
445 let component_info = ComponentIdInfoList(vec![
446 ComponentIdInfo {
447 moniker: ExtendedMoniker::parse_str("core/foo42").unwrap(),
448 instance_id: None,
449 event_id: EventCode(42),
450 label: "Foo_42".into(),
451 },
452 ComponentIdInfo {
453 moniker: ExtendedMoniker::parse_str("bootstrap/hello").unwrap(),
454 instance_id: Some(
455 InstanceId::from_str(
456 "8775ff0afe12ca578135014a5d36a7733b0f9982bcb62a888b007cb2c31a7046",
457 )
458 .unwrap(),
459 ),
460 event_id: EventCode(43),
461 label: "Hello".into(),
462 },
463 ]);
464 let config = project_template.expand(&component_info).expect("expanded template");
465 assert_eq!(
466 config,
467 ProjectConfig {
468 project_id: ProjectId(13),
469 data_sets: vec![DataSetConfig {
470 poll_rate_sec: 60,
471 metrics: vec![
472 MetricConfig {
473 selectors: vec![
474 selectors::parse_selector::<FastError>(
475 "core/foo42:root/path2:leaf2"
476 )
477 .unwrap(),
478 selectors::parse_selector::<FastError>(
479 "foo/bar:root/core\\/foo42:leaf3"
480 )
481 .unwrap(),
482 selectors::parse_selector::<FastError>(
483 "asdf/qwer:root/path4:pre-core\\/foo42-post"
484 )
485 .unwrap()
486 ],
487 metric_id: MetricId(2),
488 metric_type: MetricType::Occurrence,
489 event_codes: vec![EventCode(42)],
490 upload_once: false,
491 },
492 MetricConfig {
493 selectors: vec![
494 selectors::parse_selector::<FastError>(
495 "bootstrap/hello:root/path2:leaf2"
496 )
497 .unwrap(),
498 selectors::parse_selector::<FastError>(
499 "foo/bar:root/bootstrap\\/hello:leaf3"
500 )
501 .unwrap(),
502 selectors::parse_selector::<FastError>(
503 "asdf/qwer:root/path4:pre-bootstrap\\/hello-post"
504 )
505 .unwrap()
506 ],
507 metric_id: MetricId(2),
508 metric_type: MetricType::Occurrence,
509 event_codes: vec![EventCode(43)],
510 upload_once: false,
511 },
512 ],
513 }],
514 }
515 );
516 }
517
518 #[fuchsia::test]
519 fn index_substitution_works() {
520 let mut ids = component_id_index::Index::default();
521 let foo_bar_moniker = Moniker::parse_str("foo/bar").unwrap();
522 let qwer_asdf_moniker = Moniker::parse_str("qwer/asdf").unwrap();
523 ids.insert(IndexEntry {
524 moniker: foo_bar_moniker,
525 instance_id: "1234123412341234123412341234123412341234123412341234123412341234"
526 .parse::<InstanceId>()
527 .unwrap(),
528 ignore_duplicate_id: false,
529 })
530 .unwrap();
531 ids.insert(IndexEntry {
532 moniker: qwer_asdf_moniker,
533 instance_id: "1234abcd1234abcd123412341234123412341234123412341234123412341234"
534 .parse::<InstanceId>()
535 .unwrap(),
536 ignore_duplicate_id: false,
537 })
538 .unwrap();
539 let mut components = ComponentIdInfoList(vec![
540 ComponentIdInfo {
541 moniker: "baz/quux".try_into().unwrap(),
542 event_id: EventCode(101),
543 label: "bq".into(),
544 instance_id: None,
545 },
546 ComponentIdInfo {
547 moniker: "foo/bar".try_into().unwrap(),
548 event_id: EventCode(102),
549 label: "fb".into(),
550 instance_id: None,
551 },
552 ]);
553 components.add_instance_ids(&ids);
554 let moniker_template = "fizz/buzz:root/info/{MONIKER}/data";
555 let id_template = "fizz/buzz:root/info/{INSTANCE_ID}/data";
556 assert_eq!(
557 interpolate_template(moniker_template, &components[0]).unwrap().unwrap(),
558 "fizz/buzz:root/info/baz\\/quux/data".to_string()
559 );
560 assert_eq!(
561 interpolate_template(moniker_template, &components[1]).unwrap().unwrap(),
562 "fizz/buzz:root/info/foo\\/bar/data".to_string()
563 );
564 assert_eq!(interpolate_template(id_template, &components[0]).unwrap(), None);
565 assert_eq!(
566 interpolate_template(id_template, &components[1]).unwrap().unwrap(),
567 "fizz/buzz:root/info/1234123412341234123412341234123412341234123412341234123412341234/data"
568 );
569 }
570
571 #[derive(Debug, Deserialize, Eq, PartialEq)]
572 struct TestString(#[serde(deserialize_with = "super::one_or_many_strings")] Vec<String>);
573
574 #[fuchsia::test]
575 fn parse_valid_single_string() {
576 let json = "\"whatever-1982035*()$*H\"";
577 let data: TestString = serde_json5::from_str(json).expect("deserialize");
578 assert_eq!(data, TestString(vec!["whatever-1982035*()$*H".into()]));
579 }
580
581 #[fuchsia::test]
582 fn parse_valid_multiple_strings() {
583 let json = "[ \"core/foo:not:a:selector:root/branch:leaf\", \"core/bar:root/twig:leaf\"]";
584 let data: TestString = serde_json5::from_str(json).expect("deserialize");
585 assert_eq!(
586 data,
587 TestString(vec![
588 "core/foo:not:a:selector:root/branch:leaf".into(),
589 "core/bar:root/twig:leaf".into()
590 ])
591 );
592 }
593
594 #[fuchsia::test]
595 fn refuse_invalid_strings() {
596 let not_string = "42";
597 let bad_list = "[ 42, \"core/bar:not:a:selector:root/twig:leaf\"]";
598 serde_json5::from_str::<TestString>(not_string).expect_err("should fail");
599 serde_json5::from_str::<TestString>(bad_list).expect_err("should fail");
600 }
601}