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, Default)]
142pub enum DurationUnit {
143 NanoSecond,
145 MicroSecond,
147 #[default]
149 MilliSecond,
150 Second,
152 Minute,
154 Hour,
156 Day,
158 Week,
160}
161
162impl DurationUnit {
163 pub fn as_str(&self) -> &'static str {
165 match self {
166 Self::NanoSecond => "nanosecond",
167 Self::MicroSecond => "microsecond",
168 Self::MilliSecond => "millisecond",
169 Self::Second => "second",
170 Self::Minute => "minute",
171 Self::Hour => "hour",
172 Self::Day => "day",
173 Self::Week => "week",
174 }
175 }
176}
177
178impl fmt::Display for DurationUnit {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 f.write_str(self.as_str())
181 }
182}
183
184#[derive(Clone, Copy, Debug)]
186pub struct ParseMetricUnitError(());
187
188#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
193pub enum InformationUnit {
194 Bit,
198 #[default]
200 Byte,
201 KiloByte,
203 KibiByte,
205 MegaByte,
207 MebiByte,
209 GigaByte,
211 GibiByte,
213 TeraByte,
215 TebiByte,
217 PetaByte,
219 PebiByte,
221 ExaByte,
223 ExbiByte,
225}
226
227impl InformationUnit {
228 pub fn as_str(&self) -> &'static str {
230 match self {
231 Self::Bit => "bit",
232 Self::Byte => "byte",
233 Self::KiloByte => "kilobyte",
234 Self::KibiByte => "kibibyte",
235 Self::MegaByte => "megabyte",
236 Self::MebiByte => "mebibyte",
237 Self::GigaByte => "gigabyte",
238 Self::GibiByte => "gibibyte",
239 Self::TeraByte => "terabyte",
240 Self::TebiByte => "tebibyte",
241 Self::PetaByte => "petabyte",
242 Self::PebiByte => "pebibyte",
243 Self::ExaByte => "exabyte",
244 Self::ExbiByte => "exbibyte",
245 }
246 }
247}
248
249impl fmt::Display for InformationUnit {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 f.write_str(self.as_str())
252 }
253}
254
255#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
259pub enum FractionUnit {
260 #[default]
262 Ratio,
263 Percent,
265}
266
267impl FractionUnit {
268 pub fn as_str(&self) -> &'static str {
270 match self {
271 Self::Ratio => "ratio",
272 Self::Percent => "percent",
273 }
274 }
275}
276
277impl fmt::Display for FractionUnit {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 f.write_str(self.as_str())
280 }
281}
282
283const CUSTOM_UNIT_MAX_SIZE: usize = 15;
284
285fn is_word_char(c: u8) -> bool {
287 matches!(c, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'_')
288}
289
290#[derive(Clone, Copy, Eq, PartialEq, Hash)]
292pub struct CustomUnit([u8; CUSTOM_UNIT_MAX_SIZE]);
293
294impl CustomUnit {
295 pub fn parse(s: &str) -> Result<Self, ParseMetricUnitError> {
299 if s.is_empty() || s.bytes().any(|c| !is_word_char(c)) {
300 return Err(ParseMetricUnitError(()));
301 }
302
303 let mut unit = Self([0; CUSTOM_UNIT_MAX_SIZE]);
304 let slice = unit.0.get_mut(..s.len()).ok_or(ParseMetricUnitError(()))?;
305 slice.copy_from_slice(s.as_bytes());
306 unit.0.make_ascii_lowercase();
307 Ok(unit)
308 }
309
310 #[inline]
312 pub fn as_str(&self) -> &str {
313 unsafe { std::str::from_utf8_unchecked(&self.0).trim_end_matches('\0') }
316 }
317}
318
319impl fmt::Debug for CustomUnit {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 self.as_str().fmt(f)
322 }
323}
324
325impl fmt::Display for CustomUnit {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 self.as_str().fmt(f)
328 }
329}
330
331impl std::str::FromStr for CustomUnit {
332 type Err = ParseMetricUnitError;
333
334 fn from_str(s: &str) -> Result<Self, Self::Err> {
335 Self::parse(s)
336 }
337}
338
339impl std::ops::Deref for CustomUnit {
340 type Target = str;
341
342 fn deref(&self) -> &Self::Target {
343 self.as_str()
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_empty_unit() {
353 assert_eq!(MetricUnit::None, "".parse().unwrap());
354 }
355
356 #[test]
357 fn test_custom_unit_parse() {
358 assert_eq!("foo", CustomUnit::parse("Foo").unwrap().as_str());
359 assert_eq!(
360 "0123456789abcde",
361 CustomUnit::parse("0123456789abcde").unwrap().as_str()
362 );
363 assert!(CustomUnit::parse("this_is_a_unit_that_is_too_long").is_err());
364 }
365
366 #[test]
367 fn test_custom_unit_invalid_char() {
368 assert!(CustomUnit::parse("").is_err()); assert!(CustomUnit::parse("foo bar").is_err());
370 assert!(CustomUnit::parse("foo/bar").is_err());
371 assert!(CustomUnit::parse("foo-bar").is_err());
372 assert!(CustomUnit::parse("föö").is_err());
373 }
374}