relay_base_schema/metrics/
name.rs

1use std::fmt;
2use std::sync::Arc;
3
4use serde::{Deserialize, Serialize};
5
6use crate::metrics::{MetricNamespace, MetricType};
7
8/// Optimized string represenation of a metric name.
9///
10/// The contained name does not need to be valid MRI, but it usually is.
11///
12/// The metric name can be efficiently cloned.
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
14#[serde(transparent)]
15pub struct MetricName(Arc<str>);
16
17impl MetricName {
18    /// Extracts the type from a well formed MRI.
19    ///
20    /// If the contained metric name is not a well formed MRI this function returns `None`.
21    ///
22    /// # Examples
23    ///
24    /// ```
25    /// use relay_base_schema::metrics::{MetricName, MetricType};
26    ///
27    /// let name = MetricName::from("cfoo");
28    /// assert!(name.try_type().is_none());
29    /// let name = MetricName::from("c:custom/foo@none");
30    /// assert_eq!(name.try_type(), Some(MetricType::Counter));
31    /// let name = MetricName::from("d:custom/foo@none");
32    /// assert_eq!(name.try_type(), Some(MetricType::Distribution));
33    /// let name = MetricName::from("s:custom/foo@none");
34    /// assert_eq!(name.try_type(), Some(MetricType::Set));
35    /// let name = MetricName::from("g:custom/foo@none");
36    /// assert_eq!(name.try_type(), Some(MetricType::Gauge));
37    /// ```
38    pub fn try_type(&self) -> Option<MetricType> {
39        match self.0.as_bytes().get(..2) {
40            Some(b"c:") => Some(MetricType::Counter),
41            Some(b"d:") => Some(MetricType::Distribution),
42            Some(b"s:") => Some(MetricType::Set),
43            Some(b"g:") => Some(MetricType::Gauge),
44            _ => None,
45        }
46    }
47
48    /// Extracts the namespace from a well formed MRI.
49    ///
50    /// Returns [`MetricNamespace::Unsupported`] if the metric name is not a well formed MRI.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use relay_base_schema::metrics::{MetricName, MetricNamespace};
56    ///
57    /// let name = MetricName::from("foo");
58    /// assert_eq!(name.namespace(), MetricNamespace::Unsupported);
59    /// let name = MetricName::from("c:custom_oops/foo@none");
60    /// assert_eq!(name.namespace(), MetricNamespace::Unsupported);
61    ///
62    /// let name = MetricName::from("c:custom/foo@none");
63    /// assert_eq!(name.namespace(), MetricNamespace::Custom);
64    /// ```
65    pub fn namespace(&self) -> MetricNamespace {
66        self.try_namespace().unwrap_or(MetricNamespace::Unsupported)
67    }
68
69    /// Extracts the namespace from a well formed MRI.
70    ///
71    /// If the contained metric name is not a well formed MRI this function returns `None`.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use relay_base_schema::metrics::{MetricName, MetricNamespace};
77    ///
78    /// let name = MetricName::from("foo");
79    /// assert!(name.try_namespace().is_none());
80    /// let name = MetricName::from("c:custom_oops/foo@none");
81    /// assert!(name.try_namespace().is_none());
82    ///
83    /// let name = MetricName::from("c:custom/foo@none");
84    /// assert_eq!(name.try_namespace(), Some(MetricNamespace::Custom));
85    /// ```
86    pub fn try_namespace(&self) -> Option<MetricNamespace> {
87        // A well formed MRI is always in the format `<type>:<namespace>/<name>[@<unit>]`,
88        // `<type>` is always a single ascii character.
89        //
90        // Skip the first two ascii characters and extract the namespace.
91        let maybe_namespace = self.0.get(2..)?.split('/').next()?;
92
93        MetricNamespace::all()
94            .into_iter()
95            .find(|namespace| maybe_namespace == namespace.as_str())
96    }
97
98    /// Extracts the metric name from a well formed MRI.
99    ///
100    /// If the contained metric name is not a well formed MRI this function returns `None`.
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// use relay_base_schema::metrics::{MetricName, MetricNamespace};
106    ///
107    /// let name = MetricName::from("foo");
108    /// assert!(name.try_name().is_none());
109    ///
110    /// let name = MetricName::from("c:custom/foo@none");
111    /// assert_eq!(name.try_name(), Some("foo"));
112    /// let name = MetricName::from("c:custom/foo");
113    /// assert_eq!(name.try_name(), Some("foo"));
114    /// ```
115    pub fn try_name(&self) -> Option<&str> {
116        // A well formed MRI is always in the format `<type>:<namespace>/<name>[@<unit>]`.
117        let after_slash = self.0.get(2..)?.split('/').nth(1)?;
118        after_slash.split('@').next()
119    }
120}
121
122impl PartialEq<str> for MetricName {
123    fn eq(&self, other: &str) -> bool {
124        self.0.as_ref() == other
125    }
126}
127
128impl fmt::Display for MetricName {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        self.0.fmt(f)
131    }
132}
133
134impl From<String> for MetricName {
135    fn from(value: String) -> Self {
136        Self(value.into())
137    }
138}
139
140impl From<Arc<str>> for MetricName {
141    fn from(value: Arc<str>) -> Self {
142        Self(value)
143    }
144}
145
146impl From<&str> for MetricName {
147    fn from(value: &str) -> Self {
148        Self(value.into())
149    }
150}
151
152impl std::ops::Deref for MetricName {
153    type Target = str;
154
155    fn deref(&self) -> &Self::Target {
156        self.0.deref()
157    }
158}
159
160impl AsRef<str> for MetricName {
161    fn as_ref(&self) -> &str {
162        self.0.as_ref()
163    }
164}
165
166impl std::borrow::Borrow<str> for MetricName {
167    fn borrow(&self) -> &str {
168        self.0.borrow()
169    }
170}