shadow_rs/utility/
units.rs

1/*!
2Types for parsing/deserializing unit values.
3
4```
5# use shadow_rs::utility::units::*;
6# use std::str::FromStr;
7let time = Time::from_str("10 min").unwrap();
8assert_eq!(time, Time::new(10, TimePrefix::Min));
9
10assert_eq!(
11    time.convert(TimePrefix::Sec).unwrap(),
12    Time::from_str("600 sec").unwrap()
13);
14```
15*/
16
17use std::borrow::Cow;
18use std::fmt::{self, Debug, Display};
19use std::str::FromStr;
20
21use once_cell::sync::Lazy;
22use regex::Regex;
23use schemars::JsonSchema;
24use serde::de::{Deserialize, Deserializer, Visitor};
25use serde::ser::{Serialize, Serializer};
26
27/// The prefix of a unit value denoting magnitude. Common prefixes are
28/// SI prefixes (nano, micro, milli, etc).
29pub trait Prefix: Clone + Copy + Default + PartialEq + FromStr + Display + Debug {
30    /// The magnitude of this prefix relative to other prefixes of this type.
31    fn relative_magnitude(&self) -> u128;
32
33    /// An integer conversion factor.
34    fn conversion_factor(&self, to: Self) -> Result<u128, String> {
35        let from_mag = self.relative_magnitude();
36        let to_mag = to.relative_magnitude();
37        if from_mag % to_mag != 0 {
38            return Err("Conversion would lose precision".to_string());
39        }
40        Ok(from_mag / to_mag)
41    }
42
43    /// A floating point conversion factor.
44    fn conversion_factor_lossy(&self, to: Self) -> f64 {
45        let from_mag = self.relative_magnitude();
46        let to_mag = to.relative_magnitude();
47        from_mag as f64 / to_mag as f64
48    }
49}
50
51/// Common SI prefixes (including base-2 prefixes since they're similar).
52#[derive(Debug, Copy, Clone, PartialEq, Eq)]
53pub enum SiPrefix {
54    Nano,
55    Micro,
56    Milli,
57    Base,
58    Kilo,
59    Kibi,
60    Mega,
61    Mebi,
62    Giga,
63    Gibi,
64    Tera,
65    Tebi,
66}
67
68impl Default for SiPrefix {
69    fn default() -> Self {
70        Self::Base
71    }
72}
73
74impl FromStr for SiPrefix {
75    type Err = String;
76
77    fn from_str(s: &str) -> Result<Self, Self::Err> {
78        match s {
79            "n" | "nano" => Ok(Self::Nano),
80            "u" | "μ" | "micro" => Ok(Self::Micro),
81            "m" | "milli" => Ok(Self::Milli),
82            "K" | "kilo" => Ok(Self::Kilo),
83            "Ki" | "kibi" => Ok(Self::Kibi),
84            "M" | "mega" => Ok(Self::Mega),
85            "Mi" | "mebi" => Ok(Self::Mebi),
86            "G" | "giga" => Ok(Self::Giga),
87            "Gi" | "gibi" => Ok(Self::Gibi),
88            "T" | "tera" => Ok(Self::Tera),
89            "Ti" | "tebi" => Ok(Self::Tebi),
90            _ => Err(
91                "Unit prefix was not one of (n|nano|u|μ|micro|m|milli|K|kilo\
92                |Ki|kibi|M|mega|Mi|mebi|G|giga|Gi|gibi|T|tera|Ti|tebi)"
93                    .to_string(),
94            ),
95        }
96    }
97}
98
99impl fmt::Display for SiPrefix {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        match self {
102            Self::Nano => write!(f, "n"),
103            Self::Micro => write!(f, "μ"),
104            Self::Milli => write!(f, "m"),
105            Self::Kilo => write!(f, "K"),
106            Self::Kibi => write!(f, "Ki"),
107            Self::Mega => write!(f, "M"),
108            Self::Mebi => write!(f, "Mi"),
109            Self::Giga => write!(f, "G"),
110            Self::Gibi => write!(f, "Gi"),
111            Self::Tera => write!(f, "T"),
112            Self::Tebi => write!(f, "Ti"),
113            Self::Base => Ok(()),
114        }
115    }
116}
117
118impl Prefix for SiPrefix {
119    fn relative_magnitude(&self) -> u128 {
120        const TEN: u128 = 10;
121        const TWO: u128 = 2;
122        const BASE: u128 = TEN.pow(9);
123        match self {
124            Self::Nano => BASE / TEN.pow(9),
125            Self::Micro => BASE / TEN.pow(6),
126            Self::Milli => BASE / TEN.pow(3),
127            Self::Base => BASE,
128            Self::Kilo => BASE * TEN.pow(3),
129            Self::Kibi => BASE * TWO.pow(10),
130            Self::Mega => BASE * TEN.pow(6),
131            Self::Mebi => BASE * TWO.pow(20),
132            Self::Giga => BASE * TEN.pow(9),
133            Self::Gibi => BASE * TWO.pow(30),
134            Self::Tera => BASE * TEN.pow(12),
135            Self::Tebi => BASE * TWO.pow(40),
136        }
137    }
138}
139
140/// Common SI prefixes larger than the base unit (including base-2 prefixes
141/// since they're similar).
142#[derive(Debug, Copy, Clone, PartialEq, Eq)]
143pub enum SiPrefixUpper {
144    Base,
145    Kilo,
146    Kibi,
147    Mega,
148    Mebi,
149    Giga,
150    Gibi,
151    Tera,
152    Tebi,
153}
154
155impl Default for SiPrefixUpper {
156    fn default() -> Self {
157        Self::Base
158    }
159}
160
161impl FromStr for SiPrefixUpper {
162    type Err = String;
163
164    fn from_str(s: &str) -> Result<Self, Self::Err> {
165        match s {
166            "K" | "kilo" => Ok(Self::Kilo),
167            "Ki" | "kibi" => Ok(Self::Kibi),
168            "M" | "mega" => Ok(Self::Mega),
169            "Mi" | "mebi" => Ok(Self::Mebi),
170            "G" | "giga" => Ok(Self::Giga),
171            "Gi" | "gibi" => Ok(Self::Gibi),
172            "T" | "tera" => Ok(Self::Tera),
173            "Ti" | "tebi" => Ok(Self::Tebi),
174            _ => Err("Unit prefix was not one of (K|kilo|Ki|kibi|M|mega|Mi|mebi\
175                |G|giga|Gi|gibi|T|tera|Ti|tebi)"
176                .to_string()),
177        }
178    }
179}
180
181impl fmt::Display for SiPrefixUpper {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            Self::Kilo => write!(f, "K"),
185            Self::Kibi => write!(f, "Ki"),
186            Self::Mega => write!(f, "M"),
187            Self::Mebi => write!(f, "Mi"),
188            Self::Giga => write!(f, "G"),
189            Self::Gibi => write!(f, "Gi"),
190            Self::Tera => write!(f, "T"),
191            Self::Tebi => write!(f, "Ti"),
192            Self::Base => Ok(()),
193        }
194    }
195}
196
197impl Prefix for SiPrefixUpper {
198    fn relative_magnitude(&self) -> u128 {
199        const TEN: u128 = 10;
200        const TWO: u128 = 2;
201        match self {
202            Self::Base => 1,
203            Self::Kilo => TEN.pow(3),
204            Self::Kibi => TWO.pow(10),
205            Self::Mega => TEN.pow(6),
206            Self::Mebi => TWO.pow(20),
207            Self::Giga => TEN.pow(9),
208            Self::Gibi => TWO.pow(30),
209            Self::Tera => TEN.pow(12),
210            Self::Tebi => TWO.pow(40),
211        }
212    }
213}
214
215/// Time units, which we pretend are prefixes for implementation simplicity. These
216/// contain both the prefix ("n", "u", "m") and the suffix ("sec", "min", "hr")
217/// and should be used with the [`Time`] unit.
218#[derive(Debug, Copy, Clone, PartialEq, Eq)]
219pub enum TimePrefix {
220    Nano,
221    Micro,
222    Milli,
223    Sec,
224    Min,
225    Hour,
226}
227
228impl Default for TimePrefix {
229    fn default() -> Self {
230        Self::Sec
231    }
232}
233
234impl FromStr for TimePrefix {
235    type Err = String;
236
237    fn from_str(s: &str) -> Result<Self, Self::Err> {
238        match s {
239            "ns" | "nanosecond" | "nanoseconds" => Ok(Self::Nano),
240            "us" | "μs" | "microsecond" | "microseconds" => Ok(Self::Micro),
241            "ms" | "millisecond" | "milliseconds" => Ok(Self::Milli),
242            "s" | "sec" | "secs" | "second" | "seconds" => Ok(Self::Sec),
243            "m" | "min" | "mins" | "minute" | "minutes" => Ok(Self::Min),
244            "h" | "hr" | "hrs" | "hour" | "hours" => Ok(Self::Hour),
245            _ => Err(
246                "Unit was not one of (ns|nanosecond|nanoseconds|us|μs|microsecond|microseconds\
247                |ms|millisecond|milliseconds|s|sec|secs|second|seconds|m|min|mins|minute|minutes\
248                |h|hr|hrs|hour|hours)"
249                    .to_string(),
250            ),
251        }
252    }
253}
254
255impl fmt::Display for TimePrefix {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        match self {
258            Self::Nano => write!(f, "ns"),
259            Self::Micro => write!(f, "μs"),
260            Self::Milli => write!(f, "ms"),
261            Self::Sec => write!(f, "sec"),
262            Self::Min => write!(f, "min"),
263            Self::Hour => write!(f, "hour"),
264        }
265    }
266}
267
268impl Prefix for TimePrefix {
269    fn relative_magnitude(&self) -> u128 {
270        const TEN: u128 = 10;
271        const BASE: u128 = TEN.pow(9);
272        match self {
273            Self::Nano => BASE / TEN.pow(9),
274            Self::Micro => BASE / TEN.pow(6),
275            Self::Milli => BASE / TEN.pow(3),
276            Self::Sec => BASE,
277            Self::Min => BASE * 60,
278            Self::Hour => BASE * 60 * 60,
279        }
280    }
281}
282
283/// Time units larger than the base unit, which we pretend are prefixes for
284/// implementation simplicity. These really contain the unit suffix ("sec",
285/// "min", "hr") and should be used with the [`Time`] unit.
286#[derive(Debug, Copy, Clone, PartialEq, Eq)]
287pub enum TimePrefixUpper {
288    Sec,
289    Min,
290    Hour,
291}
292
293impl Default for TimePrefixUpper {
294    fn default() -> Self {
295        Self::Sec
296    }
297}
298
299impl FromStr for TimePrefixUpper {
300    type Err = String;
301
302    fn from_str(s: &str) -> Result<Self, Self::Err> {
303        match s {
304            "s" | "sec" | "secs" | "second" | "seconds" => Ok(Self::Sec),
305            "m" | "min" | "mins" | "minute" | "minutes" => Ok(Self::Min),
306            "h" | "hr" | "hrs" | "hour" | "hours" => Ok(Self::Hour),
307            _ => Err("Unit prefix was not one of (s|sec|secs|second|seconds\
308                |m|min|mins|minute|minutes|h|hr|hrs|hour|hours)"
309                .to_string()),
310        }
311    }
312}
313
314impl fmt::Display for TimePrefixUpper {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        match self {
317            Self::Sec => write!(f, "sec"),
318            Self::Min => write!(f, "min"),
319            Self::Hour => write!(f, "hour"),
320        }
321    }
322}
323
324impl Prefix for TimePrefixUpper {
325    fn relative_magnitude(&self) -> u128 {
326        match self {
327            Self::Sec => 1,
328            Self::Min => 60,
329            Self::Hour => 60 * 60,
330        }
331    }
332}
333
334macro_rules! visit_fn {
335    ($fn_name:ident, $type:ty) => {
336        fn $fn_name<E>(self, x: $type) -> Result<Self::Value, E>
337        where
338            E: serde::de::Error,
339        {
340            Ok(Self::Value::new(
341                x.try_into().map_err(serde::de::Error::custom)?,
342                T::default(),
343            ))
344        }
345    };
346}
347
348macro_rules! unit_impl {
349    ($name:ident, $type:ident, $suffixes:tt) => {
350        impl<T: Prefix> $name<T> {
351            pub fn new(value: $type, prefix: T) -> Self {
352                Self { value, prefix }
353            }
354        }
355
356        impl<T: Prefix> Default for $name<T> {
357            fn default() -> Self {
358                Self::new(0, T::default())
359            }
360        }
361
362        impl<T: Prefix> Unit for $name<T> {
363            type U = $type;
364            type T = T;
365
366            fn value(&self) -> Self::U {
367                self.value
368            }
369
370            fn prefix(&self) -> Self::T {
371                self.prefix
372            }
373
374            fn suffixes() -> &'static [&'static str] {
375                &$suffixes
376            }
377
378            fn convert(&self, prefix: Self::T) -> Result<Self, String> {
379                let factor = self.prefix.conversion_factor(prefix)?;
380                let factor = factor.try_into().unwrap();
381                Ok(Self::new(
382                    Self::U::checked_mul(self.value, factor).ok_or(format!(
383                        "The resulting value is outside of the bounds [{}, {}]",
384                        Self::U::MIN,
385                        Self::U::MAX,
386                    ))?,
387                    prefix,
388                ))
389            }
390
391            fn convert_lossy(&self, prefix: Self::T) -> Self {
392                let factor = self.prefix.conversion_factor_lossy(prefix);
393                Self::new(
394                    (self.value as f64 * factor).round() as Self::U,
395                    prefix,
396                )
397            }
398        }
399
400        impl<T: Prefix> Display for $name<T> {
401            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402                write!(f, "{} {}{}", self.value(), self.prefix(), Self::suffixes()[0])
403            }
404        }
405
406        impl<T: Prefix> FromStr for $name<T>
407        where
408            <T as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
409        {
410            type Err = Box<dyn std::error::Error + Send + Sync>;
411
412            fn from_str(s: &str) -> Result<Self, Self::Err> {
413                static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([+-]?[0-9\.]*)\s*(.*)$").unwrap());
414
415                let captures = RE.captures(s).ok_or("Unable to identify value and unit")?;
416                let (value, unit) = (
417                    captures.get(1).unwrap().as_str().trim(),
418                    captures.get(2).unwrap().as_str().trim(),
419                );
420
421                // try removing all suffixes
422                let prefix = $name::<T>::suffixes()
423                    .iter()
424                    .map(|suffix| unit.strip_suffix(suffix))
425                    .find(|x| x.is_some())
426                    .flatten()
427                    .or(Some(unit))
428                    .unwrap();
429
430                let prefix = match prefix {
431                    "" => T::default(),
432                    _ => T::from_str(prefix).map_err(|x| x.to_string())?,
433                };
434
435                Ok($name::new(
436                    value.parse()?,
437                    prefix,
438                ))
439            }
440        }
441
442        impl<'de, T: Prefix> Deserialize<'de> for $name<T>
443        where
444            <T as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
445        {
446            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
447            where
448                D: Deserializer<'de>,
449            {
450                struct ValueVisitor<T> {
451                    marker: std::marker::PhantomData<T>,
452                }
453
454                impl<'de, T: Prefix> Visitor<'de> for ValueVisitor<T>
455                where
456                    <T as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
457                {
458                    type Value = $name<T>;
459
460                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
461                        formatter.write_str(stringify!(struct $name<T>))
462                    }
463
464                    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
465                    where
466                        E: serde::de::Error,
467                    {
468                        Self::Value::from_str(s).map_err(serde::de::Error::custom)
469                    }
470
471                    visit_fn!(visit_u64, u64);
472                    visit_fn!(visit_u32, u32);
473                    visit_fn!(visit_u8, u8);
474                    visit_fn!(visit_i64, i64);
475                    visit_fn!(visit_i32, i32);
476                    visit_fn!(visit_i8, i8);
477                }
478
479                deserializer.deserialize_any(ValueVisitor {
480                    marker: std::marker::PhantomData,
481                })
482            }
483        }
484
485        impl<T: Prefix> Serialize for $name<T> {
486            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
487            where
488                S: Serializer,
489            {
490                serializer.serialize_str(&self.to_string())
491            }
492        }
493
494        impl<T: Prefix> JsonSchema for $name<T> {
495            fn schema_name() -> Cow<'static, str> {
496                stringify!($name).into()
497            }
498
499            fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
500                schemars::json_schema!({
501                    "title": stringify!($name),
502                    "type": ["string"],
503                })
504            }
505        }
506    };
507}
508
509/// A unit containing a value (ex: an integer), a prefix (ex: an enum), and
510/// allowed constant suffix strings.
511pub trait Unit: Sized {
512    type U;
513    type T: Prefix;
514
515    /// The value of the unit in the size of its current prefix.
516    fn value(&self) -> Self::U;
517
518    /// The current prefix.
519    fn prefix(&self) -> Self::T;
520
521    fn suffixes() -> &'static [&'static str];
522
523    /// Convert value to a different prefix, but return an error if the conversion
524    /// cannot be done without possibly losing precision.
525    fn convert(&self, prefix: Self::T) -> Result<Self, String>
526    where
527        Self: Sized;
528
529    /// Convert value to a different prefix, even if it loses precision.
530    fn convert_lossy(&self, prefix: Self::T) -> Self
531    where
532        Self: Sized;
533}
534
535/// An amount of time. Should only use the time prefix types ([`TimePrefix`] and
536/// [`TimePrefixUpper`]) with this type.
537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
538pub struct Time<T: Prefix> {
539    value: u64,
540    prefix: T,
541}
542
543// Since our time prefix types ([TimePrefix] and [TimePrefixUpper]) aren't
544// really prefixes, time units don't have a suffix.
545unit_impl!(Time, u64, [""]);
546
547impl From<Time<TimePrefix>> for std::time::Duration {
548    fn from(time: Time<TimePrefix>) -> Self {
549        std::time::Duration::from_nanos(time.convert(TimePrefix::Nano).unwrap().value())
550    }
551}
552
553impl From<Time<TimePrefixUpper>> for std::time::Duration {
554    fn from(time: Time<TimePrefixUpper>) -> Self {
555        std::time::Duration::from_secs(time.convert(TimePrefixUpper::Sec).unwrap().value())
556    }
557}
558
559/// A number of bytes.
560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
561pub struct Bytes<T: Prefix> {
562    pub value: u64,
563    pub prefix: T,
564}
565
566unit_impl!(Bytes, u64, ["B", "byte", "bytes"]);
567
568/// A throughput in bits-per-second.
569#[derive(Debug, Clone, Copy, PartialEq, Eq)]
570pub struct BitsPerSec<T: Prefix> {
571    pub value: u64,
572    pub prefix: T,
573}
574
575unit_impl!(BitsPerSec, u64, ["bit", "bits"]);
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580
581    #[test]
582    fn test_parse_string() {
583        assert_eq!(
584            Time::from_str("10").unwrap(),
585            Time::new(10, TimePrefix::Sec)
586        );
587        assert_eq!(
588            Time::from_str("10 s").unwrap(),
589            Time::new(10, TimePrefix::Sec)
590        );
591        assert_eq!(
592            Time::from_str("10s").unwrap(),
593            Time::new(10, TimePrefix::Sec)
594        );
595        assert_eq!(
596            Time::from_str("10   s").unwrap(),
597            Time::new(10, TimePrefix::Sec)
598        );
599        assert_eq!(
600            Time::from_str("10sec").unwrap(),
601            Time::new(10, TimePrefix::Sec)
602        );
603        assert_eq!(
604            Time::from_str("10  m").unwrap(),
605            Time::new(10, TimePrefix::Min)
606        );
607        assert_eq!(
608            Time::from_str("10  min").unwrap(),
609            Time::new(10, TimePrefix::Min)
610        );
611        assert_eq!(
612            Time::from_str("10 ms").unwrap(),
613            Time::new(10, TimePrefix::Milli)
614        );
615        assert_eq!(
616            Time::from_str("10 μs").unwrap(),
617            Time::new(10, TimePrefix::Micro)
618        );
619        assert_eq!(
620            Time::from_str("10 millisecond").unwrap(),
621            Time::new(10, TimePrefix::Milli)
622        );
623        assert_eq!(
624            Time::from_str("10 milliseconds").unwrap(),
625            Time::new(10, TimePrefix::Milli)
626        );
627
628        assert!(Time::<TimePrefix>::from_str("-10 ms").is_err());
629        assert!(Time::<TimePrefix>::from_str("abc 10 ms").is_err());
630        assert!(Time::<TimePrefix>::from_str("10.5 ms").is_err());
631        assert!(Time::<TimePrefix>::from_str("10 abc").is_err());
632        assert!(Time::<TimePrefixUpper>::from_str("10 ms").is_err());
633
634        assert_eq!(
635            Bytes::from_str("10").unwrap(),
636            Bytes::new(10, SiPrefixUpper::Base)
637        );
638        assert_eq!(
639            Bytes::from_str("10 B").unwrap(),
640            Bytes::new(10, SiPrefixUpper::Base)
641        );
642        assert_eq!(
643            Bytes::from_str("10B").unwrap(),
644            Bytes::new(10, SiPrefixUpper::Base)
645        );
646        assert_eq!(
647            Bytes::from_str("10   B").unwrap(),
648            Bytes::new(10, SiPrefixUpper::Base)
649        );
650        assert_eq!(
651            Bytes::from_str("10  KB").unwrap(),
652            Bytes::new(10, SiPrefixUpper::Kilo)
653        );
654        assert_eq!(
655            Bytes::from_str("10 KiB").unwrap(),
656            Bytes::new(10, SiPrefixUpper::Kibi)
657        );
658        assert_eq!(
659            Bytes::from_str("10 MB").unwrap(),
660            Bytes::new(10, SiPrefixUpper::Mega)
661        );
662        assert_eq!(
663            Bytes::from_str("10 megabyte").unwrap(),
664            Bytes::new(10, SiPrefixUpper::Mega)
665        );
666        assert_eq!(
667            Bytes::from_str("10 megabytes").unwrap(),
668            Bytes::new(10, SiPrefixUpper::Mega)
669        );
670
671        assert!(Bytes::<SiPrefixUpper>::from_str("-10 KB").is_err());
672        assert!(Bytes::<SiPrefixUpper>::from_str("abc 10 KB").is_err());
673        assert!(Bytes::<SiPrefixUpper>::from_str("10.5 KB").is_err());
674        assert!(Bytes::<SiPrefixUpper>::from_str("10 abc").is_err());
675        assert!(Bytes::<SiPrefixUpper>::from_str("10 mB").is_err());
676        assert!(Bytes::<SiPrefixUpper>::from_str("10 Megabyte").is_err());
677
678        assert_eq!(
679            BitsPerSec::from_str("10").unwrap(),
680            BitsPerSec::new(10, SiPrefixUpper::Base)
681        );
682        assert_eq!(
683            BitsPerSec::from_str("10 bit").unwrap(),
684            BitsPerSec::new(10, SiPrefixUpper::Base)
685        );
686        assert_eq!(
687            BitsPerSec::from_str("10bit").unwrap(),
688            BitsPerSec::new(10, SiPrefixUpper::Base)
689        );
690        assert_eq!(
691            BitsPerSec::from_str("10   bit").unwrap(),
692            BitsPerSec::new(10, SiPrefixUpper::Base)
693        );
694        assert_eq!(
695            BitsPerSec::from_str("10  Kbit").unwrap(),
696            BitsPerSec::new(10, SiPrefixUpper::Kilo)
697        );
698        assert_eq!(
699            BitsPerSec::from_str("10 Kibit").unwrap(),
700            BitsPerSec::new(10, SiPrefixUpper::Kibi)
701        );
702        assert_eq!(
703            BitsPerSec::from_str("10 Mbit").unwrap(),
704            BitsPerSec::new(10, SiPrefixUpper::Mega)
705        );
706        assert_eq!(
707            BitsPerSec::from_str("10 megabit").unwrap(),
708            BitsPerSec::new(10, SiPrefixUpper::Mega)
709        );
710        assert_eq!(
711            BitsPerSec::from_str("10 megabits").unwrap(),
712            BitsPerSec::new(10, SiPrefixUpper::Mega)
713        );
714
715        assert!(BitsPerSec::<SiPrefixUpper>::from_str("-10 Kbit").is_err());
716        assert!(BitsPerSec::<SiPrefixUpper>::from_str("abc 10 Kbit").is_err());
717        assert!(BitsPerSec::<SiPrefixUpper>::from_str("10.5 Kbit").is_err());
718        assert!(BitsPerSec::<SiPrefixUpper>::from_str("10 abc").is_err());
719        assert!(BitsPerSec::<SiPrefixUpper>::from_str("10 mbit").is_err());
720    }
721
722    #[test]
723    fn test_conversion() {
724        let time = Time::from_str("70 min").unwrap();
725
726        assert_eq!(
727            time.convert(TimePrefix::Sec).unwrap(),
728            Time::from_str("4200 sec").unwrap()
729        );
730        assert!(time.convert(TimePrefix::Hour).is_err());
731
732        assert_eq!(
733            time.convert_lossy(TimePrefix::Sec),
734            Time::from_str("4200 sec").unwrap()
735        );
736        assert_eq!(
737            time.convert_lossy(TimePrefix::Hour),
738            Time::from_str("1 hour").unwrap()
739        );
740
741        let bw = BitsPerSec::from_str("1024 Kbit").unwrap();
742
743        assert_eq!(
744            bw.convert(SiPrefixUpper::Base).unwrap(),
745            BitsPerSec::from_str("1024000 bit").unwrap()
746        );
747        assert!(bw.convert(SiPrefixUpper::Kibi).is_err());
748
749        assert_eq!(
750            bw.convert_lossy(SiPrefixUpper::Base),
751            BitsPerSec::from_str("1024000 bit").unwrap()
752        );
753        assert_eq!(
754            bw.convert_lossy(SiPrefixUpper::Kibi),
755            BitsPerSec::from_str("1000 Kibit").unwrap()
756        );
757    }
758
759    #[test]
760    fn test_time_conversion() {
761        let time = Time::<TimePrefixUpper>::from_str("70 min").unwrap();
762        assert_eq!(
763            std::time::Duration::from_secs(70 * 60),
764            std::time::Duration::from(time)
765        );
766
767        let time = Time::new(1_000_000_123, TimePrefix::Nano);
768        assert_eq!(
769            std::time::Duration::new(1, 123),
770            std::time::Duration::from(time)
771        );
772    }
773
774    #[test]
775    fn schema() {
776        assert_eq!(
777            Time::<TimePrefix>::json_schema(&mut schemars::SchemaGenerator::default()),
778            schemars::json_schema!({
779                "title": "Time",
780                "type": ["string"],
781            }),
782        );
783    }
784}