relay_base_schema/metrics/
units.rs1use std::fmt;
2
3use relay_protocol::{Annotated, Empty, ErrorKind, FromValue, IntoValue, SkipSerialization, Value};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
13pub enum MetricUnit {
14 Duration(DurationUnit),
16 Information(InformationUnit),
18 Fraction(FractionUnit),
20 Custom(CustomUnit),
22 #[default]
24 None,
25}
26
27impl MetricUnit {
28 pub fn is_none(&self) -> bool {
30 matches!(self, Self::None)
31 }
32
33 pub fn as_str(&self) -> &str {
35 match self {
36 MetricUnit::Duration(u) => u.as_str(),
37 MetricUnit::Information(u) => u.as_str(),
38 MetricUnit::Fraction(u) => u.as_str(),
39 MetricUnit::Custom(u) => u.as_str(),
40 MetricUnit::None => "none",
41 }
42 }
43}
44
45impl fmt::Display for MetricUnit {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 MetricUnit::Duration(u) => u.fmt(f),
49 MetricUnit::Information(u) => u.fmt(f),
50 MetricUnit::Fraction(u) => u.fmt(f),
51 MetricUnit::Custom(u) => u.fmt(f),
52 MetricUnit::None => f.write_str("none"),
53 }
54 }
55}
56
57impl std::str::FromStr for MetricUnit {
58 type Err = ParseMetricUnitError;
59
60 fn from_str(s: &str) -> Result<Self, Self::Err> {
61 Ok(match s {
62 "nanosecond" | "ns" => Self::Duration(DurationUnit::NanoSecond),
63 "microsecond" => Self::Duration(DurationUnit::MicroSecond),
64 "millisecond" | "ms" => Self::Duration(DurationUnit::MilliSecond),
65 "second" | "s" => Self::Duration(DurationUnit::Second),
66 "minute" => Self::Duration(DurationUnit::Minute),
67 "hour" => Self::Duration(DurationUnit::Hour),
68 "day" => Self::Duration(DurationUnit::Day),
69 "week" => Self::Duration(DurationUnit::Week),
70
71 "bit" => Self::Information(InformationUnit::Bit),
72 "byte" => Self::Information(InformationUnit::Byte),
73 "kilobyte" => Self::Information(InformationUnit::KiloByte),
74 "kibibyte" => Self::Information(InformationUnit::KibiByte),
75 "megabyte" => Self::Information(InformationUnit::MegaByte),
76 "mebibyte" => Self::Information(InformationUnit::MebiByte),
77 "gigabyte" => Self::Information(InformationUnit::GigaByte),
78 "gibibyte" => Self::Information(InformationUnit::GibiByte),
79 "terabyte" => Self::Information(InformationUnit::TeraByte),
80 "tebibyte" => Self::Information(InformationUnit::TebiByte),
81 "petabyte" => Self::Information(InformationUnit::PetaByte),
82 "pebibyte" => Self::Information(InformationUnit::PebiByte),
83 "exabyte" => Self::Information(InformationUnit::ExaByte),
84 "exbibyte" => Self::Information(InformationUnit::ExbiByte),
85
86 "ratio" => Self::Fraction(FractionUnit::Ratio),
87 "percent" => Self::Fraction(FractionUnit::Percent),
88
89 "" | "none" => Self::None,
90 _ => Self::Custom(CustomUnit::parse(s)?),
91 })
92 }
93}
94
95relay_common::impl_str_serde!(MetricUnit, "a metric unit string");
96
97impl Empty for MetricUnit {
98 #[inline]
99 fn is_empty(&self) -> bool {
100 false
102 }
103}
104
105impl FromValue for MetricUnit {
106 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
107 match String::from_value(value) {
108 Annotated(Some(value), mut meta) => match value.parse() {
109 Ok(unit) => Annotated(Some(unit), meta),
110 Err(_) => {
111 meta.add_error(ErrorKind::InvalidData);
112 meta.set_original_value(Some(value));
113 Annotated(None, meta)
114 }
115 },
116 Annotated(None, meta) => Annotated(None, meta),
117 }
118 }
119}
120
121impl IntoValue for MetricUnit {
122 fn into_value(self) -> Value
123 where
124 Self: Sized,
125 {
126 Value::String(self.to_string())
127 }
128
129 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
130 where
131 Self: Sized,
132 S: serde::Serializer,
133 {
134 serde::Serialize::serialize(self.as_str(), s)
135 }
136}
137
138#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
142pub enum DurationUnit {
143 NanoSecond,
145 MicroSecond,
147 MilliSecond,
149 Second,
151 Minute,
153 Hour,
155 Day,
157 Week,
159}
160
161impl DurationUnit {
162 pub fn as_str(&self) -> &'static str {
164 match self {
165 Self::NanoSecond => "nanosecond",
166 Self::MicroSecond => "microsecond",
167 Self::MilliSecond => "millisecond",
168 Self::Second => "second",
169 Self::Minute => "minute",
170 Self::Hour => "hour",
171 Self::Day => "day",
172 Self::Week => "week",
173 }
174 }
175}
176
177impl Default for DurationUnit {
178 fn default() -> Self {
179 Self::MilliSecond
180 }
181}
182
183impl fmt::Display for DurationUnit {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 f.write_str(self.as_str())
186 }
187}
188
189#[derive(Clone, Copy, Debug)]
191pub struct ParseMetricUnitError(());
192
193#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
198pub enum InformationUnit {
199 Bit,
203 Byte,
205 KiloByte,
207 KibiByte,
209 MegaByte,
211 MebiByte,
213 GigaByte,
215 GibiByte,
217 TeraByte,
219 TebiByte,
221 PetaByte,
223 PebiByte,
225 ExaByte,
227 ExbiByte,
229}
230
231impl InformationUnit {
232 pub fn as_str(&self) -> &'static str {
234 match self {
235 Self::Bit => "bit",
236 Self::Byte => "byte",
237 Self::KiloByte => "kilobyte",
238 Self::KibiByte => "kibibyte",
239 Self::MegaByte => "megabyte",
240 Self::MebiByte => "mebibyte",
241 Self::GigaByte => "gigabyte",
242 Self::GibiByte => "gibibyte",
243 Self::TeraByte => "terabyte",
244 Self::TebiByte => "tebibyte",
245 Self::PetaByte => "petabyte",
246 Self::PebiByte => "pebibyte",
247 Self::ExaByte => "exabyte",
248 Self::ExbiByte => "exbibyte",
249 }
250 }
251}
252
253impl Default for InformationUnit {
254 fn default() -> Self {
255 Self::Byte
256 }
257}
258
259impl fmt::Display for InformationUnit {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 f.write_str(self.as_str())
262 }
263}
264
265#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
269pub enum FractionUnit {
270 Ratio,
272 Percent,
274}
275
276impl FractionUnit {
277 pub fn as_str(&self) -> &'static str {
279 match self {
280 Self::Ratio => "ratio",
281 Self::Percent => "percent",
282 }
283 }
284}
285
286impl Default for FractionUnit {
287 fn default() -> Self {
288 Self::Ratio
289 }
290}
291
292impl fmt::Display for FractionUnit {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 f.write_str(self.as_str())
295 }
296}
297
298const CUSTOM_UNIT_MAX_SIZE: usize = 15;
299
300fn is_word_char(c: u8) -> bool {
302 matches!(c, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'_')
303}
304
305#[derive(Clone, Copy, Eq, PartialEq, Hash)]
307pub struct CustomUnit([u8; CUSTOM_UNIT_MAX_SIZE]);
308
309impl CustomUnit {
310 pub fn parse(s: &str) -> Result<Self, ParseMetricUnitError> {
314 if s.is_empty() || s.bytes().any(|c| !is_word_char(c)) {
315 return Err(ParseMetricUnitError(()));
316 }
317
318 let mut unit = Self([0; CUSTOM_UNIT_MAX_SIZE]);
319 let slice = unit.0.get_mut(..s.len()).ok_or(ParseMetricUnitError(()))?;
320 slice.copy_from_slice(s.as_bytes());
321 unit.0.make_ascii_lowercase();
322 Ok(unit)
323 }
324
325 #[inline]
327 pub fn as_str(&self) -> &str {
328 unsafe { std::str::from_utf8_unchecked(&self.0).trim_end_matches('\0') }
331 }
332}
333
334impl fmt::Debug for CustomUnit {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 self.as_str().fmt(f)
337 }
338}
339
340impl fmt::Display for CustomUnit {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 self.as_str().fmt(f)
343 }
344}
345
346impl std::str::FromStr for CustomUnit {
347 type Err = ParseMetricUnitError;
348
349 fn from_str(s: &str) -> Result<Self, Self::Err> {
350 Self::parse(s)
351 }
352}
353
354impl std::ops::Deref for CustomUnit {
355 type Target = str;
356
357 fn deref(&self) -> &Self::Target {
358 self.as_str()
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_empty_unit() {
368 assert_eq!(MetricUnit::None, "".parse().unwrap());
369 }
370
371 #[test]
372 fn test_custom_unit_parse() {
373 assert_eq!("foo", CustomUnit::parse("Foo").unwrap().as_str());
374 assert_eq!(
375 "0123456789abcde",
376 CustomUnit::parse("0123456789abcde").unwrap().as_str()
377 );
378 assert!(CustomUnit::parse("this_is_a_unit_that_is_too_long").is_err());
379 }
380
381 #[test]
382 fn test_custom_unit_invalid_char() {
383 assert!(CustomUnit::parse("").is_err()); assert!(CustomUnit::parse("foo bar").is_err());
385 assert!(CustomUnit::parse("foo/bar").is_err());
386 assert!(CustomUnit::parse("foo-bar").is_err());
387 assert!(CustomUnit::parse("föö").is_err());
388 }
389}