1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(mismatched_lifetime_syntaxes)] use derive_more::{Add, Display, Div, From, FromStr, Mul};
48
49use serde::{Deserialize, Serialize};
50use std::time::Duration;
51use thiserror::Error;
52
53#[cfg(feature = "memquota-memcost")]
54use {derive_deftly::Deftly, tor_memquota::derive_deftly_template_HasMemoryCost};
55
56#[derive(Debug, Clone, PartialEq, Eq, Error)]
58#[non_exhaustive]
59pub enum Error {
60 #[error("Value {0} was below the lower bound {1} for this type")]
62 BelowLowerBound(i32, i32),
63 #[error("Value {0} was above the lower bound {1} for this type")]
65 AboveUpperBound(i32, i32),
66 #[error("Tried to convert a negative value to an unsigned type")]
68 Negative,
69 #[error("Value could not be represented as an i32")]
72 Unrepresentable,
73 #[error("Integer overflow")]
75 Overflow,
76 #[error("No value is valid for this type")]
78 Uninhabited,
79}
80
81#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
94#[cfg_attr(
95 feature = "memquota-memcost",
96 derive(Deftly),
97 derive_deftly(HasMemoryCost)
98)]
99pub struct BoundedInt32<const LOWER: i32, const UPPER: i32> {
100 value: i32,
102}
103
104impl<const LOWER: i32, const UPPER: i32> BoundedInt32<LOWER, UPPER> {
105 pub const LOWER: i32 = LOWER;
107 pub const UPPER: i32 = UPPER;
109
110 fn unchecked_new(value: i32) -> Self {
112 assert!(LOWER <= UPPER); BoundedInt32 { value }
115 }
116
117 pub const fn lower(&self) -> i32 {
121 LOWER
122 }
123
124 pub const fn upper(&self) -> i32 {
128 UPPER
129 }
130
131 pub fn get(&self) -> i32 {
136 self.value
137 }
138
139 pub fn saturating_new(val: i32) -> Self {
143 Self::unchecked_new(Self::clamp(val))
144 }
145
146 pub fn checked_new(val: i32) -> Result<Self, Error> {
149 if val > UPPER {
150 Err(Error::AboveUpperBound(val, UPPER))
151 } else if val < LOWER {
152 Err(Error::BelowLowerBound(val, LOWER))
153 } else {
154 Ok(BoundedInt32::unchecked_new(val))
155 }
156 }
157
158 fn clamp(val: i32) -> i32 {
160 Ord::clamp(val, LOWER, UPPER)
161 }
162
163 pub fn saturating_from(val: i32) -> Self {
170 Self::unchecked_new(Self::clamp(val))
171 }
172
173 pub fn saturating_from_str(s: &str) -> Result<Self, Error> {
180 if UPPER < LOWER {
181 return Err(Error::Uninhabited);
183 }
184 let val: i32 = s.parse().map_err(|_| Error::Unrepresentable)?;
185 Ok(Self::saturating_from(val))
186 }
187}
188
189impl<const L: i32, const U: i32> std::fmt::Display for BoundedInt32<L, U> {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "{}", self.value)
192 }
193}
194
195impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for i32 {
196 fn from(val: BoundedInt32<L, U>) -> i32 {
197 val.value
198 }
199}
200
201impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for f64 {
202 fn from(val: BoundedInt32<L, U>) -> f64 {
203 val.value.into()
204 }
205}
206
207impl<const L: i32, const H: i32> TryFrom<i32> for BoundedInt32<L, H> {
208 type Error = Error;
209 fn try_from(val: i32) -> Result<Self, Self::Error> {
210 Self::checked_new(val)
211 }
212}
213
214impl<const L: i32, const H: i32> std::str::FromStr for BoundedInt32<L, H> {
215 type Err = Error;
216 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
217 Self::checked_new(s.parse().map_err(|_| Error::Unrepresentable)?)
218 }
219}
220
221impl From<BoundedInt32<0, 1>> for bool {
222 fn from(val: BoundedInt32<0, 1>) -> bool {
223 val.value == 1
224 }
225}
226
227impl From<BoundedInt32<0, 255>> for u8 {
228 fn from(val: BoundedInt32<0, 255>) -> u8 {
229 val.value as u8
230 }
231}
232
233impl<const L: i32, const H: i32> From<BoundedInt32<L, H>> for u32 {
234 fn from(val: BoundedInt32<L, H>) -> u32 {
235 val.value as u32
236 }
237}
238
239impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for u64 {
240 type Error = Error;
241 fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
242 if val.value < 0 {
243 Err(Error::Negative)
244 } else {
245 Ok(val.value as u64)
246 }
247 }
248}
249
250impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for usize {
251 type Error = Error;
252 fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
253 if val.value < 0 {
254 Err(Error::Negative)
255 } else {
256 Ok(val.value as usize)
257 }
258 }
259}
260
261#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
266pub struct Percentage<T: Copy + Into<f64>> {
267 value: T,
269}
270
271impl<T: Copy + Into<f64>> Percentage<T> {
272 pub fn new(value: T) -> Self {
274 Self { value }
275 }
276
277 pub fn as_fraction(self) -> f64 {
291 self.value.into() / 100.0
292 }
293
294 pub fn as_percent(self) -> T {
307 self.value
308 }
309}
310
311impl<const H: i32, const L: i32> TryFrom<i32> for Percentage<BoundedInt32<H, L>> {
312 type Error = Error;
313 fn try_from(v: i32) -> Result<Self, Error> {
314 Ok(Percentage::new(v.try_into()?))
315 }
316}
317
318#[derive(
322 Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash,
323)]
324pub struct IntegerMilliseconds<T> {
328 value: T,
330}
331
332impl<T> IntegerMilliseconds<T> {
333 pub fn new(value: T) -> Self {
335 IntegerMilliseconds { value }
336 }
337
338 pub fn as_millis(self) -> T {
343 self.value
344 }
345
346 pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerMilliseconds<U>, E>
358 where
359 F: FnOnce(T) -> Result<U, E>,
360 {
361 Ok(IntegerMilliseconds::new(f(self.value)?))
362 }
363}
364
365impl<T: TryInto<u64>> TryFrom<IntegerMilliseconds<T>> for Duration {
366 type Error = <T as TryInto<u64>>::Error;
367 fn try_from(val: IntegerMilliseconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
368 Ok(Self::from_millis(val.value.try_into()?))
369 }
370}
371
372impl<const H: i32, const L: i32> TryFrom<i32> for IntegerMilliseconds<BoundedInt32<H, L>> {
373 type Error = Error;
374 fn try_from(v: i32) -> Result<Self, Error> {
375 Ok(IntegerMilliseconds::new(v.try_into()?))
376 }
377}
378
379#[derive(
380 Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash,
381)]
382pub struct IntegerSeconds<T> {
386 value: T,
388}
389
390impl<T> IntegerSeconds<T> {
391 pub fn new(value: T) -> Self {
393 IntegerSeconds { value }
394 }
395
396 pub fn as_secs(self) -> T {
401 self.value
402 }
403
404 pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerSeconds<U>, E>
414 where
415 F: FnOnce(T) -> Result<U, E>,
416 {
417 Ok(IntegerSeconds::new(f(self.value)?))
418 }
419}
420
421impl<T: TryInto<u64>> TryFrom<IntegerSeconds<T>> for Duration {
422 type Error = <T as TryInto<u64>>::Error;
423 fn try_from(val: IntegerSeconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
424 Ok(Self::from_secs(val.value.try_into()?))
425 }
426}
427
428impl<const H: i32, const L: i32> TryFrom<i32> for IntegerSeconds<BoundedInt32<H, L>> {
429 type Error = Error;
430 fn try_from(v: i32) -> Result<Self, Error> {
431 Ok(IntegerSeconds::new(v.try_into()?))
432 }
433}
434
435#[derive(Deserialize, Serialize)] #[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
437pub struct IntegerMinutes<T> {
441 value: T,
443}
444
445impl<T> IntegerMinutes<T> {
446 pub fn new(value: T) -> Self {
448 IntegerMinutes { value }
449 }
450
451 pub fn as_minutes(self) -> T {
456 self.value
457 }
458
459 pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerMinutes<U>, E>
469 where
470 F: FnOnce(T) -> Result<U, E>,
471 {
472 Ok(IntegerMinutes::new(f(self.value)?))
473 }
474}
475
476impl<T: TryInto<u64>> TryFrom<IntegerMinutes<T>> for Duration {
477 type Error = Error;
478 fn try_from(val: IntegerMinutes<T>) -> Result<Self, Error> {
479 const SECONDS_PER_MINUTE: u64 = 60;
481 let minutes: u64 = val.value.try_into().map_err(|_| Error::Overflow)?;
482 let seconds = minutes
483 .checked_mul(SECONDS_PER_MINUTE)
484 .ok_or(Error::Overflow)?;
485 Ok(Self::from_secs(seconds))
486 }
487}
488
489impl<const H: i32, const L: i32> TryFrom<i32> for IntegerMinutes<BoundedInt32<H, L>> {
490 type Error = Error;
491 fn try_from(v: i32) -> Result<Self, Error> {
492 Ok(IntegerMinutes::new(v.try_into()?))
493 }
494}
495
496#[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
497pub struct IntegerDays<T> {
501 value: T,
503}
504
505impl<T> IntegerDays<T> {
506 pub fn new(value: T) -> Self {
508 IntegerDays { value }
509 }
510
511 pub fn as_days(self) -> T {
516 self.value
517 }
518
519 pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerDays<U>, E>
529 where
530 F: FnOnce(T) -> Result<U, E>,
531 {
532 Ok(IntegerDays::new(f(self.value)?))
533 }
534}
535
536impl<T: TryInto<u64>> TryFrom<IntegerDays<T>> for Duration {
537 type Error = Error;
538 fn try_from(val: IntegerDays<T>) -> Result<Self, Error> {
539 const SECONDS_PER_DAY: u64 = 86400;
541 let days: u64 = val.value.try_into().map_err(|_| Error::Overflow)?;
542 let seconds = days.checked_mul(SECONDS_PER_DAY).ok_or(Error::Overflow)?;
543 Ok(Self::from_secs(seconds))
544 }
545}
546
547impl<const H: i32, const L: i32> TryFrom<i32> for IntegerDays<BoundedInt32<H, L>> {
548 type Error = Error;
549 fn try_from(v: i32) -> Result<Self, Error> {
550 Ok(IntegerDays::new(v.try_into()?))
551 }
552}
553
554#[derive(Clone, Copy, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
558pub struct SendMeVersion(u8);
559
560impl SendMeVersion {
561 pub fn new(value: u8) -> Self {
563 SendMeVersion(value)
564 }
565
566 pub fn get(&self) -> u8 {
568 self.0
569 }
570}
571
572impl TryFrom<i32> for SendMeVersion {
573 type Error = Error;
574 fn try_from(v: i32) -> Result<Self, Error> {
575 let val_u8 = BoundedInt32::<0, 255>::checked_new(v)?;
576 Ok(SendMeVersion::new(val_u8.get() as u8))
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 #![allow(clippy::unwrap_used)]
583 use float_cmp::assert_approx_eq;
584
585 use super::*;
586
587 type TestFoo = BoundedInt32<1, 5>;
588 type TestBar = BoundedInt32<-45, 17>;
589
590 #[test]
592 fn entire_range_parsed() {
593 let x: TestFoo = "1".parse().unwrap();
594 assert!(x.get() == 1);
595 let x: TestFoo = "2".parse().unwrap();
596 assert!(x.get() == 2);
597 let x: TestFoo = "3".parse().unwrap();
598 assert!(x.get() == 3);
599 let x: TestFoo = "4".parse().unwrap();
600 assert!(x.get() == 4);
601 let x: TestFoo = "5".parse().unwrap();
602 assert!(x.get() == 5);
603 }
604
605 #[test]
606 fn saturating() {
607 let x: TestFoo = TestFoo::saturating_new(1000);
608 let x_val: i32 = x.into();
609 assert!(x_val == TestFoo::UPPER);
610 let x: TestFoo = TestFoo::saturating_new(0);
611 let x_val: i32 = x.into();
612 assert!(x_val == TestFoo::LOWER);
613 }
614 #[test]
615 fn saturating_string() {
616 let x: TestFoo = TestFoo::saturating_from_str("1000").unwrap();
617 let x_val: i32 = x.into();
618 assert!(x_val == TestFoo::UPPER);
619 let x: TestFoo = TestFoo::saturating_from_str("0").unwrap();
620 let x_val: i32 = x.into();
621 assert!(x_val == TestFoo::LOWER);
622 }
623
624 #[test]
625 #[should_panic]
626 fn uninhabited_saturating_new() {
627 let _: BoundedInt32<10, 5> = BoundedInt32::saturating_new(7);
629 }
630
631 #[test]
632 fn uninhabited_from_string() {
633 let v: Result<BoundedInt32<10, 5>, Error> = BoundedInt32::saturating_from_str("7");
634 assert!(matches!(v, Err(Error::Uninhabited)));
635 }
636
637 #[test]
638 fn errors_correct() {
639 let x: Result<TestBar, Error> = "1000".parse();
640 assert!(x.unwrap_err() == Error::AboveUpperBound(1000, TestBar::UPPER));
641 let x: Result<TestBar, Error> = "-1000".parse();
642 assert!(x.unwrap_err() == Error::BelowLowerBound(-1000, TestBar::LOWER));
643 let x: Result<TestBar, Error> = "xyz".parse();
644 assert!(x.unwrap_err() == Error::Unrepresentable);
645 }
646
647 #[test]
648 fn display() {
649 let v = BoundedInt32::<99, 1000>::checked_new(345).unwrap();
650 assert_eq!(v.to_string(), "345".to_string());
651 }
652
653 #[test]
654 #[should_panic]
655 fn checked_too_high() {
656 let _: TestBar = "1000".parse().unwrap();
657 }
658
659 #[test]
660 #[should_panic]
661 fn checked_too_low() {
662 let _: TestBar = "-46".parse().unwrap();
663 }
664
665 #[test]
666 fn bounded_to_u64() {
667 let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
668 let u: u64 = b.try_into().unwrap();
669 assert_eq!(u, 77);
670
671 let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(-77).unwrap();
672 let u: Result<u64, Error> = b.try_into();
673 assert!(u.is_err());
674 }
675
676 #[test]
677 fn bounded_to_f64() {
678 let x: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
679 let f: f64 = x.into();
680 assert_approx_eq!(f64, f, 77.0);
681 }
682
683 #[test]
684 fn bounded_from_i32() {
685 let x: Result<BoundedInt32<-100, 100>, _> = 50.try_into();
686 let y: i32 = x.unwrap().into();
687 assert_eq!(y, 50);
688
689 let x: Result<BoundedInt32<-100, 100>, _> = 1000.try_into();
690 assert!(x.is_err());
691 }
692
693 #[test]
694 fn into_bool() {
695 let zero: BoundedInt32<0, 1> = BoundedInt32::saturating_from(0);
696 let one: BoundedInt32<0, 1> = BoundedInt32::saturating_from(1);
697
698 let f: bool = zero.into();
699 let t: bool = one.into();
700 assert!(!f);
701 assert!(t);
702 }
703
704 #[test]
705 fn into_u8() {
706 let zero: BoundedInt32<0, 255> = BoundedInt32::saturating_from(0);
707 let one: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1);
708 let ninety: BoundedInt32<0, 255> = BoundedInt32::saturating_from(90);
709 let max: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1000);
710
711 let a: u8 = zero.into();
712 let b: u8 = one.into();
713 let c: u8 = ninety.into();
714 let d: u8 = max.into();
715
716 assert_eq!(a, 0);
717 assert_eq!(b, 1);
718 assert_eq!(c, 90);
719 assert_eq!(d, 255);
720 }
721
722 #[test]
723 fn into_u32() {
724 let zero: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(0);
725 let one: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1);
726 let ninety: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(90);
727 let max: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1000);
728
729 assert_eq!(u32::from(zero), 0);
730 assert_eq!(u32::from(one), 1);
731 assert_eq!(u32::from(ninety), 90);
732 assert_eq!(u32::from(max), 1000);
733
734 let zero: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(0);
735 let one: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1);
736 let ninety: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(90);
737 let max: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1000);
738
739 assert_eq!(u32::from(zero), 1);
740 assert_eq!(u32::from(one), 1);
741 assert_eq!(u32::from(ninety), 90);
742 assert_eq!(u32::from(max), 1000);
743 }
744
745 #[test]
746 fn try_into_usize() {
747 let b0: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(0);
748 let b100: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(100);
749 let bn5: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(-5);
750 assert_eq!(usize::try_from(b0), Ok(0_usize));
751 assert_eq!(usize::try_from(b100), Ok(100_usize));
752 assert_eq!(usize::try_from(bn5), Err(Error::Negative));
753 }
754
755 #[test]
756 fn percents() {
757 type Pct = Percentage<u8>;
758 let p = Pct::new(100);
759 assert_eq!(p.as_percent(), 100);
760 assert_approx_eq!(f64, p.as_fraction(), 1.0);
761
762 let p = Pct::new(0);
763 assert_eq!(p.as_percent(), 0);
764 assert_approx_eq!(f64, p.as_fraction(), 0.0);
765
766 let p = Pct::new(25);
767 assert_eq!(p.as_percent(), 25);
768 assert_eq!(p.clone(), p);
769 assert_approx_eq!(f64, p.as_fraction(), 0.25);
770
771 type BPct = Percentage<BoundedInt32<0, 100>>;
772 assert_eq!(BPct::try_from(99).unwrap().as_percent().get(), 99);
773 }
774
775 #[test]
776 fn milliseconds() {
777 type Msec = IntegerMilliseconds<i32>;
778
779 let ms = Msec::new(500);
780 let d: Result<Duration, _> = ms.try_into();
781 assert_eq!(d.unwrap(), Duration::from_millis(500));
782 assert_eq!(Duration::try_from(ms * 2).unwrap(), Duration::from_secs(1));
783
784 let ms = Msec::new(-100);
785 let d: Result<Duration, _> = ms.try_into();
786 assert!(d.is_err());
787
788 type BMSec = IntegerMilliseconds<BoundedInt32<0, 1000>>;
789 let half_sec = BMSec::try_from(500).unwrap();
790 assert_eq!(
791 Duration::try_from(half_sec).unwrap(),
792 Duration::from_millis(500)
793 );
794 assert!(BMSec::try_from(1001).is_err());
795 }
796
797 #[test]
798 fn seconds() {
799 type Sec = IntegerSeconds<i32>;
800
801 let ms = Sec::new(500);
802 let d: Result<Duration, _> = ms.try_into();
803 assert_eq!(d.unwrap(), Duration::from_secs(500));
804
805 let ms = Sec::new(-100);
806 let d: Result<Duration, _> = ms.try_into();
807 assert!(d.is_err());
808
809 type BSec = IntegerSeconds<BoundedInt32<0, 3600>>;
810 let half_hour = BSec::try_from(1800).unwrap();
811 assert_eq!(
812 Duration::try_from(half_hour).unwrap(),
813 Duration::from_secs(1800)
814 );
815 assert!(BSec::try_from(9999).is_err());
816 assert_eq!(half_hour.clone(), half_hour);
817 }
818
819 #[test]
820 fn minutes() {
821 type Min = IntegerMinutes<i32>;
822
823 let t = Min::new(500);
824 let d: Duration = t.try_into().unwrap();
825 assert_eq!(d, Duration::from_secs(500 * 60));
826
827 let t = Min::new(-100);
828 let d: Result<Duration, _> = t.try_into();
829 assert_eq!(d, Err(Error::Overflow));
830
831 let t = IntegerMinutes::<u64>::new(u64::MAX);
832 let d: Result<Duration, _> = t.try_into();
833 assert_eq!(d, Err(Error::Overflow));
834
835 type BMin = IntegerMinutes<BoundedInt32<10, 30>>;
836 assert_eq!(
837 BMin::new(17_i32.try_into().unwrap()),
838 BMin::try_from(17).unwrap()
839 );
840 }
841
842 #[test]
843 fn days() {
844 type Days = IntegerDays<i32>;
845
846 let t = Days::new(500);
847 let d: Duration = t.try_into().unwrap();
848 assert_eq!(d, Duration::from_secs(500 * 86400));
849
850 let t = Days::new(-100);
851 let d: Result<Duration, _> = t.try_into();
852 assert_eq!(d, Err(Error::Overflow));
853
854 let t = IntegerDays::<u64>::new(u64::MAX);
855 let d: Result<Duration, _> = t.try_into();
856 assert_eq!(d, Err(Error::Overflow));
857
858 type BDays = IntegerDays<BoundedInt32<10, 30>>;
859 assert_eq!(
860 BDays::new(17_i32.try_into().unwrap()),
861 BDays::try_from(17).unwrap()
862 );
863 }
864
865 #[test]
866 fn sendme() {
867 let smv = SendMeVersion::new(5);
868 assert_eq!(smv.get(), 5);
869 assert_eq!(smv.clone().get(), 5);
870 assert_eq!(smv, SendMeVersion::try_from(5).unwrap());
871 }
872}