relay_event_normalization/eap/
trace_metric.rs1use std::{borrow::Cow, sync::LazyLock};
4
5use regex::Regex;
6use relay_base_schema::metrics::MetricUnit;
7use relay_event_schema::protocol::TraceMetric;
8use relay_protocol::Annotated;
9
10#[derive(Debug, thiserror::Error, PartialEq, Eq)]
12#[error("Metric has an invalid name")]
13pub struct InvalidMetricName;
14
15pub fn normalize_metric_name(metric: &mut TraceMetric) -> Result<(), InvalidMetricName> {
22 static NORMALIZE_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new("[^a-zA-Z0-9_.]+").unwrap());
23
24 let name = metric.name.value_mut().as_mut();
25 let Some(name) = name.filter(|s| !s.trim().is_empty()) else {
26 return Err(InvalidMetricName);
27 };
28
29 if let Cow::Owned(new_name) = NORMALIZE_RE.replace_all(name, "_") {
30 *name = new_name;
31 }
32
33 Ok(())
34}
35
36pub fn normalize_metric_unit(metric: &mut TraceMetric) {
40 if let Some(MetricUnit::Custom(_)) = metric.unit.value() {
41 metric.unit = Annotated::new(MetricUnit::None);
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use relay_protocol::Annotated;
48
49 use super::*;
50
51 fn metric(name: impl Into<String>) -> TraceMetric {
52 TraceMetric {
53 name: Annotated::new(name.into()),
54 ..Default::default()
55 }
56 }
57
58 macro_rules! assert_metric_name {
59 ($name:expr, err) => {{
60 assert_eq!(
61 normalize_metric_name(&mut metric($name)),
62 Err(InvalidMetricName)
63 )
64 }};
65 ($name:expr, $expected:expr) => {{
66 let mut metric = metric($name);
67 assert_eq!(normalize_metric_name(&mut metric), Ok(()));
68 let name = metric.name.value_mut().as_mut().unwrap();
69 assert_eq!(name, $expected);
70 }};
71 }
72
73 #[test]
74 fn test_normalize_name_invalid() {
75 assert_metric_name!("", err);
76 assert_metric_name!(" ", err);
77 }
78
79 #[test]
80 fn test_normalize_metric_name() {
81 assert_metric_name!("foo.bar123", "foo.bar123");
82 assert_metric_name!("foo bar", "foo_bar");
83 assert_metric_name!("foo!@#bar", "foo_bar");
84 assert_metric_name!(" foo.bar ", "_foo.bar_");
85 assert_metric_name!("unicøøde", "unic_de");
86 }
87
88 #[test]
89 fn test_normalize_metric_unit_strips_custom() {
90 use relay_base_schema::metrics::DurationUnit;
91
92 let mut m = metric("test");
93 m.unit = Annotated::new(MetricUnit::Custom("customunit".parse().unwrap()));
94 normalize_metric_unit(&mut m);
95 assert_eq!(m.unit.value(), Some(&MetricUnit::None));
96
97 let mut m = metric("test");
98 m.unit = Annotated::new(MetricUnit::Duration(DurationUnit::Second));
99 normalize_metric_unit(&mut m);
100 assert_eq!(
101 m.unit.value(),
102 Some(&MetricUnit::Duration(DurationUnit::Second))
103 );
104
105 let mut m = metric("test");
106 m.unit = Annotated::new(MetricUnit::None);
107 normalize_metric_unit(&mut m);
108 assert_eq!(m.unit.value(), Some(&MetricUnit::None));
109 }
110}