1use 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
27pub trait Prefix: Clone + Copy + Default + PartialEq + FromStr + Display + Debug {
30 fn relative_magnitude(&self) -> u128;
32
33 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 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#[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#[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#[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#[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 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
509pub trait Unit: Sized {
512 type U;
513 type T: Prefix;
514
515 fn value(&self) -> Self::U;
517
518 fn prefix(&self) -> Self::T;
520
521 fn suffixes() -> &'static [&'static str];
522
523 fn convert(&self, prefix: Self::T) -> Result<Self, String>
526 where
527 Self: Sized;
528
529 fn convert_lossy(&self, prefix: Self::T) -> Self
531 where
532 Self: Sized;
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
538pub struct Time<T: Prefix> {
539 value: u64,
540 prefix: T,
541}
542
543unit_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#[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#[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}