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