Skip to main content

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