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