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.is_multiple_of(to_mag) {
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(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#[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#[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#[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 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
489pub trait Unit: Sized {
492 type U;
493 type T: Prefix;
494
495 fn value(&self) -> Self::U;
497
498 fn prefix(&self) -> Self::T;
500
501 fn suffixes() -> &'static [&'static str];
502
503 fn convert(&self, prefix: Self::T) -> Result<Self, String>
506 where
507 Self: Sized;
508
509 fn convert_lossy(&self, prefix: Self::T) -> Self
511 where
512 Self: Sized;
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq)]
518pub struct Time<T: Prefix> {
519 value: u64,
520 prefix: T,
521}
522
523unit_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#[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#[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}