Skip to main content

sentry_types/protocol/
unit.rs

1//! Contains a [`Unit`] type, which encodes all
2//! [units we support](https://develop.sentry.dev/sdk/foundations/state-management/scopes/attributes/#units).
3//! Implementations to convert from common string types are provided.
4
5use std::borrow::Cow;
6
7use serde::{Deserialize, Deserializer, Serialize};
8
9/// A unit for a metric.
10///
11/// Recognized units are explicitly enumerated, while other units can be set using the
12/// [`Unit::Other`] variant.
13#[derive(Debug, Clone, PartialEq, Serialize)]
14#[serde(rename_all(serialize = "lowercase"))]
15#[expect(missing_docs)] // Most of the variants are self-explanatory
16#[non_exhaustive]
17pub enum Unit {
18    Nanosecond,
19    Microsecond,
20    Millisecond,
21    Second,
22    Minute,
23    Hour,
24    Day,
25    Week,
26    Bit,
27    Byte,
28    Kilobyte,
29    Kibibyte,
30    Megabyte,
31    Mebibyte,
32    Gigabyte,
33    Gibibyte,
34    Terabyte,
35    Tebibyte,
36    Petabyte,
37    Pebibyte,
38    Exabyte,
39    Exbibyte,
40    Ratio,
41    Percent,
42    /// Any other unit, which may not be recognized by the Sentry UI.
43    ///
44    /// We advise against constructing this variant directly; instead, rely on the `From`
45    /// implementations to convert from a `String`, `Cow<'static, str>`, or `&'static str`,
46    /// as these implementations normalize to the known units, including to any units we
47    /// may add in the future as we add them.
48    #[serde(untagged)]
49    Other(Cow<'static, str>),
50}
51
52impl From<Cow<'static, str>> for Unit {
53    /// Convert a [`Cow<'static, str>`] to a [`Unit`]. Known units (including standard symbols,
54    /// such as "MB" for "megabyte" or "ms" for "millisecond") are converted to the appropriate
55    /// enum variant, with other unknown units being mapped to [`Unit::Other`].
56    #[inline]
57    fn from(value: Cow<'static, str>) -> Self {
58        Self::new(value)
59    }
60}
61
62impl From<&'static str> for Unit {
63    /// Convert a [`&'static str`](str) to a [`Unit`]. Known units (including standard symbols,
64    /// such as "MB" for "megabyte" or "ms" for "millisecond") are converted to the appropriate
65    /// enum variant, with other unknown units being mapped to [`Unit::Other`].
66    #[inline]
67    fn from(value: &'static str) -> Self {
68        Self::new(value)
69    }
70}
71
72impl From<String> for Unit {
73    /// Convert a [`String`] to a [`Unit`]. Known units (including standard symbols,
74    /// such as "MB" for "megabyte" or "ms" for "millisecond") are converted to the appropriate
75    /// enum variant, with other unknown units being mapped to [`Unit::Other`].
76    #[inline]
77    fn from(value: String) -> Self {
78        Self::new(value)
79    }
80}
81
82impl<'de> Deserialize<'de> for Unit {
83    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
84    where
85        D: Deserializer<'de>,
86    {
87        Ok(Unit::new(String::deserialize(deserializer)?))
88    }
89}
90
91impl Unit {
92    fn new<U>(value: U) -> Self
93    where
94        U: Into<Cow<'static, str>>,
95    {
96        let value = value.into();
97
98        match value.as_ref() {
99            "nanosecond" | "Nanosecond" | "nanoseconds" | "Nanoseconds" | "ns" => Self::Nanosecond,
100            // Note: μs (with μ: U+03BC, GREEK SMALL LETTER MU) and µs (with µ: U+00B5, MICRO SIGN)
101            // look nearly identical, both convert to micro.
102            "microsecond" | "Microsecond" | "microseconds" | "Microseconds" | "μs" | "µs" => {
103                Self::Microsecond
104            }
105            "millisecond" | "Millisecond" | "milliseconds" | "Milliseconds" | "ms" => {
106                Self::Millisecond
107            }
108            "second" | "Second" | "seconds" | "Seconds" | "s" => Self::Second,
109            "minute" | "Minute" | "minutes" | "Minutes" | "min" => Self::Minute,
110            "hour" | "Hour" | "hours" | "Hours" | "h" => Self::Hour,
111            "day" | "Day" | "days" | "Days" | "d" => Self::Day,
112            "week" | "Week" | "weeks" | "Weeks" => Self::Week,
113            "bit" | "Bit" | "bits" | "Bits" | "b" => Self::Bit,
114            "byte" | "Byte" | "bytes" | "Bytes" | "B" => Self::Byte,
115            "kilobyte" | "Kilobyte" | "kilobytes" | "Kilobytes" | "kB" => Self::Kilobyte,
116            "kibibyte" | "Kibibyte" | "kibibytes" | "Kibibytes" | "KiB" => Self::Kibibyte,
117            "megabyte" | "Megabyte" | "megabytes" | "Megabytes" | "MB" => Self::Megabyte,
118            "mebibyte" | "Mebibyte" | "mebibytes" | "Mebibytes" | "MiB" => Self::Mebibyte,
119            "gigabyte" | "Gigabyte" | "gigabytes" | "Gigabytes" | "GB" => Self::Gigabyte,
120            "gibibyte" | "Gibibyte" | "gibibytes" | "Gibibytes" | "GiB" => Self::Gibibyte,
121            "terabyte" | "Terabyte" | "terabytes" | "Terabytes" | "TB" => Self::Terabyte,
122            "tebibyte" | "Tebibyte" | "tebibytes" | "Tebibytes" | "TiB" => Self::Tebibyte,
123            "petabyte" | "Petabyte" | "petabytes" | "Petabytes" | "PB" => Self::Petabyte,
124            "pebibyte" | "Pebibyte" | "pebibytes" | "Pebibytes" | "PiB" => Self::Pebibyte,
125            "exabyte" | "Exabyte" | "exabytes" | "Exabytes" | "EB" => Self::Exabyte,
126            "exbibyte" | "Exbibyte" | "exbibytes" | "Exbibytes" | "EiB" => Self::Exbibyte,
127            "ratio" | "Ratio" => Self::Ratio,
128            "percent" | "Percent" | "%" => Self::Percent,
129            _ => Self::Other(value),
130        }
131    }
132}
133
134#[cfg(test)]
135mod test {
136    use super::*;
137
138    /// Test that "μs" (with U+03BC) resolves as microseconds
139    #[test]
140    fn greek_small_letter_mu_resolves_as_microseconds() {
141        let unit = Unit::from("μs");
142        assert_eq!(unit, Unit::Microsecond);
143    }
144
145    /// Test that µs (with U+00B5) also resolves as microseconds.
146    #[test]
147    fn micro_sign_resolves_as_microseconds() {
148        let unit = Unit::from("µs");
149        assert_eq!(unit, Unit::Microsecond);
150    }
151}