relay_event_normalization/eap/
trace_metric.rs1use std::{borrow::Cow, sync::LazyLock};
4
5use regex::Regex;
6use relay_event_schema::protocol::TraceMetric;
7
8#[derive(Debug, thiserror::Error, PartialEq, Eq)]
10#[error("Metric has an invalid name")]
11pub struct InvalidMetricName;
12
13pub fn normalize_metric_name(metric: &mut TraceMetric) -> Result<(), InvalidMetricName> {
20 static NORMALIZE_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new("[^a-zA-Z0-9_.]+").unwrap());
21
22 let name = metric.name.value_mut().as_mut();
23 let Some(name) = name.filter(|s| !s.trim().is_empty()) else {
24 return Err(InvalidMetricName);
25 };
26
27 if let Cow::Owned(new_name) = NORMALIZE_RE.replace_all(name, "_") {
28 *name = new_name;
29 }
30
31 Ok(())
32}
33
34#[cfg(test)]
35mod tests {
36 use relay_protocol::Annotated;
37
38 use super::*;
39
40 fn metric(name: impl Into<String>) -> TraceMetric {
41 TraceMetric {
42 name: Annotated::new(name.into()),
43 ..Default::default()
44 }
45 }
46
47 macro_rules! assert_metric_name {
48 ($name:expr, err) => {{
49 assert_eq!(
50 normalize_metric_name(&mut metric($name)),
51 Err(InvalidMetricName)
52 )
53 }};
54 ($name:expr, $expected:expr) => {{
55 let mut metric = metric($name);
56 assert_eq!(normalize_metric_name(&mut metric), Ok(()));
57 let name = metric.name.value_mut().as_mut().unwrap();
58 assert_eq!(name, $expected);
59 }};
60 }
61
62 #[test]
63 fn test_normalize_name_invalid() {
64 assert_metric_name!("", err);
65 assert_metric_name!(" ", err);
66 }
67
68 #[test]
69 fn test_normalize_metric_name() {
70 assert_metric_name!("foo.bar123", "foo.bar123");
71 assert_metric_name!("foo bar", "foo_bar");
72 assert_metric_name!("foo!@#bar", "foo_bar");
73 assert_metric_name!(" foo.bar ", "_foo.bar_");
74 assert_metric_name!("unicøøde", "unic_de");
75 }
76}