tor_units/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![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)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
45
46use derive_more::{Add, Display, Div, From, FromStr, Mul};
47
48use std::time::Duration;
49use thiserror::Error;
50
51#[cfg(feature = "memquota-memcost")]
52use {derive_deftly::Deftly, tor_memquota::derive_deftly_template_HasMemoryCost};
53
54/// Conversion errors from converting a value into a [`BoundedInt32`].
55#[derive(Debug, Clone, PartialEq, Eq, Error)]
56#[non_exhaustive]
57pub enum Error {
58    /// A passed value was below the lower bound for the type.
59    #[error("Value {0} was below the lower bound {1} for this type")]
60    BelowLowerBound(i32, i32),
61    /// A passed value was above the upper bound for the type.
62    #[error("Value {0} was above the lower bound {1} for this type")]
63    AboveUpperBound(i32, i32),
64    /// Tried to convert a negative value to an unsigned type.
65    #[error("Tried to convert a negative value to an unsigned type")]
66    Negative,
67    /// Tried to parse a value that was not representable as the
68    /// underlying type.
69    #[error("Value could not be represented as an i32")]
70    Unrepresentable,
71    /// We encountered some kind of integer overflow when converting a number.
72    #[error("Integer overflow")]
73    Overflow,
74    /// Tried to instantiate an uninhabited type.
75    #[error("No value is valid for this type")]
76    Uninhabited,
77}
78
79/// A 32-bit signed integer with a restricted range.
80///
81/// This type holds an i32 value such that `LOWER` <= value <= `UPPER`
82///
83/// # Limitations
84///
85/// If you try to instantiate this type with LOWER > UPPER, you will
86/// get an uninhabitable type.  It would be better if we could check that at
87/// compile time, and prevent such types from being named.
88//
89// [TODO: If you need a Bounded* for some type other than i32, ask nickm:
90// he has an implementation kicking around.]
91#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
92#[cfg_attr(
93    feature = "memquota-memcost",
94    derive(Deftly),
95    derive_deftly(HasMemoryCost)
96)]
97pub struct BoundedInt32<const LOWER: i32, const UPPER: i32> {
98    /// Interior Value
99    value: i32,
100}
101
102impl<const LOWER: i32, const UPPER: i32> BoundedInt32<LOWER, UPPER> {
103    /// Lower bound
104    pub const LOWER: i32 = LOWER;
105    /// Upper bound
106    pub const UPPER: i32 = UPPER;
107
108    /// Private constructor function for this type.
109    fn unchecked_new(value: i32) -> Self {
110        assert!(LOWER <= UPPER); //The compiler optimizes this out, no run-time cost.
111
112        BoundedInt32 { value }
113    }
114
115    /// Return the lower bound value of this bounded i32.
116    ///
117    /// This always return [`Self::LOWER`].
118    pub const fn lower(&self) -> i32 {
119        LOWER
120    }
121
122    /// Return the lower bound value of this bounded i32.
123    ///
124    /// This always return [`Self::LOWER`].
125    pub const fn upper(&self) -> i32 {
126        UPPER
127    }
128
129    /// Return the underlying i32 value.
130    ///
131    /// This value will always be between [`Self::LOWER`] and [`Self::UPPER`],
132    /// inclusive.
133    pub fn get(&self) -> i32 {
134        self.value
135    }
136
137    /// If `val` is within range, return a new `BoundedInt32` wrapping
138    /// it; otherwise, clamp it to the upper or lower bound as
139    /// appropriate.
140    pub fn saturating_new(val: i32) -> Self {
141        Self::unchecked_new(Self::clamp(val))
142    }
143
144    /// If `val` is an acceptable value inside the range for this type,
145    /// return a new [`BoundedInt32`].  Otherwise return an error.
146    pub fn checked_new(val: i32) -> Result<Self, Error> {
147        if val > UPPER {
148            Err(Error::AboveUpperBound(val, UPPER))
149        } else if val < LOWER {
150            Err(Error::BelowLowerBound(val, LOWER))
151        } else {
152            Ok(BoundedInt32::unchecked_new(val))
153        }
154    }
155
156    /// This private function clamps an input to the acceptable range.
157    fn clamp(val: i32) -> i32 {
158        Ord::clamp(val, LOWER, UPPER)
159    }
160
161    /// Convert from the underlying type, clamping to the upper or
162    /// lower bound if needed.
163    ///
164    /// # Panics
165    ///
166    /// This function will panic if UPPER < LOWER.
167    pub fn saturating_from(val: i32) -> Self {
168        Self::unchecked_new(Self::clamp(val))
169    }
170
171    /// Convert from a string, clamping to the upper or lower bound if needed.
172    ///
173    /// # Limitations
174    ///
175    /// If the input is a number that cannot be represented as an i32,
176    /// then we return an error instead of clamping it.
177    pub fn saturating_from_str(s: &str) -> Result<Self, Error> {
178        if UPPER < LOWER {
179            // The compiler should optimize this block out at compile time.
180            return Err(Error::Uninhabited);
181        }
182        let val: i32 = s.parse().map_err(|_| Error::Unrepresentable)?;
183        Ok(Self::saturating_from(val))
184    }
185}
186
187impl<const L: i32, const U: i32> std::fmt::Display for BoundedInt32<L, U> {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        write!(f, "{}", self.value)
190    }
191}
192
193impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for i32 {
194    fn from(val: BoundedInt32<L, U>) -> i32 {
195        val.value
196    }
197}
198
199impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for f64 {
200    fn from(val: BoundedInt32<L, U>) -> f64 {
201        val.value.into()
202    }
203}
204
205impl<const L: i32, const H: i32> TryFrom<i32> for BoundedInt32<L, H> {
206    type Error = Error;
207    fn try_from(val: i32) -> Result<Self, Self::Error> {
208        Self::checked_new(val)
209    }
210}
211
212impl<const L: i32, const H: i32> std::str::FromStr for BoundedInt32<L, H> {
213    type Err = Error;
214    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
215        Self::checked_new(s.parse().map_err(|_| Error::Unrepresentable)?)
216    }
217}
218
219impl From<BoundedInt32<0, 1>> for bool {
220    fn from(val: BoundedInt32<0, 1>) -> bool {
221        val.value == 1
222    }
223}
224
225impl From<BoundedInt32<0, 255>> for u8 {
226    fn from(val: BoundedInt32<0, 255>) -> u8 {
227        val.value as u8
228    }
229}
230
231impl<const L: i32, const H: i32> From<BoundedInt32<L, H>> for u32 {
232    fn from(val: BoundedInt32<L, H>) -> u32 {
233        val.value as u32
234    }
235}
236
237impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for u64 {
238    type Error = Error;
239    fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
240        if val.value < 0 {
241            Err(Error::Negative)
242        } else {
243            Ok(val.value as u64)
244        }
245    }
246}
247
248impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for usize {
249    type Error = Error;
250    fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
251        if val.value < 0 {
252            Err(Error::Negative)
253        } else {
254            Ok(val.value as usize)
255        }
256    }
257}
258
259/// A percentage value represented as a number.
260///
261/// This type wraps an underlying numeric type, and ensures that callers
262/// are clear whether they want a _fraction_, or a _percentage_.
263#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
264pub struct Percentage<T: Copy + Into<f64>> {
265    /// The underlying percentage value.
266    value: T,
267}
268
269impl<T: Copy + Into<f64>> Percentage<T> {
270    /// Create a new `IntPercentage` from the underlying percentage.
271    pub fn new(value: T) -> Self {
272        Self { value }
273    }
274
275    /// Return this value as a (possibly improper) fraction.
276    ///
277    /// ```
278    /// use tor_units::Percentage;
279    /// let pct_200 = Percentage::<u8>::new(200);
280    /// let pct_100 = Percentage::<u8>::new(100);
281    /// let pct_50 = Percentage::<u8>::new(50);
282    ///
283    /// assert_eq!(pct_200.as_fraction(), 2.0);
284    /// assert_eq!(pct_100.as_fraction(), 1.0);
285    /// assert_eq!(pct_50.as_fraction(), 0.5);
286    /// // Note: don't actually compare f64 with ==.
287    /// ```
288    pub fn as_fraction(self) -> f64 {
289        self.value.into() / 100.0
290    }
291
292    /// Return this value as a percentage.
293    ///
294    /// ```
295    /// use tor_units::Percentage;
296    /// let pct_200 = Percentage::<u8>::new(200);
297    /// let pct_100 = Percentage::<u8>::new(100);
298    /// let pct_50 = Percentage::<u8>::new(50);
299    ///
300    /// assert_eq!(pct_200.as_percent(), 200);
301    /// assert_eq!(pct_100.as_percent(), 100);
302    /// assert_eq!(pct_50.as_percent(), 50);
303    /// ```
304    pub fn as_percent(self) -> T {
305        self.value
306    }
307}
308
309impl<const H: i32, const L: i32> TryFrom<i32> for Percentage<BoundedInt32<H, L>> {
310    type Error = Error;
311    fn try_from(v: i32) -> Result<Self, Error> {
312        Ok(Percentage::new(v.try_into()?))
313    }
314}
315
316// TODO: There is a bunch of code duplication among these "IntegerTimeUnits"
317// section.
318
319#[derive(
320    Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash,
321)]
322/// This type represents an integer number of milliseconds.
323///
324/// The underlying type should usually implement `TryInto<u64>`.
325pub struct IntegerMilliseconds<T> {
326    /// Interior Value. Should implement `TryInto<u64>` to be useful.
327    value: T,
328}
329
330impl<T> IntegerMilliseconds<T> {
331    /// Public Constructor
332    pub fn new(value: T) -> Self {
333        IntegerMilliseconds { value }
334    }
335
336    /// Deconstructor
337    ///
338    /// Use only in contexts where it's no longer possible to
339    /// use the Rust type system to ensure secs vs ms vs us correctness.
340    pub fn as_millis(self) -> T {
341        self.value
342    }
343
344    /// Map the inner value (useful for conversion)
345    ///
346    /// # Example
347    ///
348    /// ```
349    /// use tor_units::{BoundedInt32, IntegerMilliseconds};
350    ///
351    /// let value: IntegerMilliseconds<i32> = 42.into();
352    /// let value: IntegerMilliseconds<BoundedInt32<0,1000>>
353    ///     = value.try_map(TryInto::try_into).unwrap();
354    /// ```
355    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerMilliseconds<U>, E>
356    where
357        F: FnOnce(T) -> Result<U, E>,
358    {
359        Ok(IntegerMilliseconds::new(f(self.value)?))
360    }
361}
362
363impl<T: TryInto<u64>> TryFrom<IntegerMilliseconds<T>> for Duration {
364    type Error = <T as TryInto<u64>>::Error;
365    fn try_from(val: IntegerMilliseconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
366        Ok(Self::from_millis(val.value.try_into()?))
367    }
368}
369
370impl<const H: i32, const L: i32> TryFrom<i32> for IntegerMilliseconds<BoundedInt32<H, L>> {
371    type Error = Error;
372    fn try_from(v: i32) -> Result<Self, Error> {
373        Ok(IntegerMilliseconds::new(v.try_into()?))
374    }
375}
376
377#[derive(
378    Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash,
379)]
380/// This type represents an integer number of seconds.
381///
382/// The underlying type should usually implement `TryInto<u64>`.
383pub struct IntegerSeconds<T> {
384    /// Interior Value. Should implement `TryInto<u64>` to be useful.
385    value: T,
386}
387
388impl<T> IntegerSeconds<T> {
389    /// Public Constructor
390    pub fn new(value: T) -> Self {
391        IntegerSeconds { value }
392    }
393
394    /// Deconstructor
395    ///
396    /// Use only in contexts where it's no longer possible to
397    /// use the Rust type system to ensure secs vs ms vs us correctness.
398    pub fn as_secs(self) -> T {
399        self.value
400    }
401
402    /// Map the inner value (useful for conversion)
403    ///
404    /// ```
405    /// use tor_units::{BoundedInt32, IntegerSeconds};
406    ///
407    /// let value: IntegerSeconds<i32> = 42.into();
408    /// let value: IntegerSeconds<BoundedInt32<0,1000>>
409    ///     = value.try_map(TryInto::try_into).unwrap();
410    /// ```
411    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerSeconds<U>, E>
412    where
413        F: FnOnce(T) -> Result<U, E>,
414    {
415        Ok(IntegerSeconds::new(f(self.value)?))
416    }
417}
418
419impl<T: TryInto<u64>> TryFrom<IntegerSeconds<T>> for Duration {
420    type Error = <T as TryInto<u64>>::Error;
421    fn try_from(val: IntegerSeconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
422        Ok(Self::from_secs(val.value.try_into()?))
423    }
424}
425
426impl<const H: i32, const L: i32> TryFrom<i32> for IntegerSeconds<BoundedInt32<H, L>> {
427    type Error = Error;
428    fn try_from(v: i32) -> Result<Self, Error> {
429        Ok(IntegerSeconds::new(v.try_into()?))
430    }
431}
432
433#[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
434/// This type represents an integer number of minutes.
435///
436/// The underlying type should usually implement `TryInto<u64>`.
437pub struct IntegerMinutes<T> {
438    /// Interior Value. Should Implement `TryInto<u64>` to be useful.
439    value: T,
440}
441
442impl<T> IntegerMinutes<T> {
443    /// Public Constructor
444    pub fn new(value: T) -> Self {
445        IntegerMinutes { value }
446    }
447
448    /// Deconstructor
449    ///
450    /// Use only in contexts where it's no longer possible to
451    /// use the Rust type system to ensure secs vs ms vs us correctness.
452    pub fn as_minutes(self) -> T {
453        self.value
454    }
455
456    /// Map the inner value (useful for conversion)
457    ///
458    /// ```
459    /// use tor_units::{BoundedInt32, IntegerMinutes};
460    ///
461    /// let value: IntegerMinutes<i32> = 42.into();
462    /// let value: IntegerMinutes<BoundedInt32<0,1000>>
463    ///     = value.try_map(TryInto::try_into).unwrap();
464    /// ```
465    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerMinutes<U>, E>
466    where
467        F: FnOnce(T) -> Result<U, E>,
468    {
469        Ok(IntegerMinutes::new(f(self.value)?))
470    }
471}
472
473impl<T: TryInto<u64>> TryFrom<IntegerMinutes<T>> for Duration {
474    type Error = Error;
475    fn try_from(val: IntegerMinutes<T>) -> Result<Self, Error> {
476        /// Number of seconds in a single minute.
477        const SECONDS_PER_MINUTE: u64 = 60;
478        let minutes: u64 = val.value.try_into().map_err(|_| Error::Overflow)?;
479        let seconds = minutes
480            .checked_mul(SECONDS_PER_MINUTE)
481            .ok_or(Error::Overflow)?;
482        Ok(Self::from_secs(seconds))
483    }
484}
485
486impl<const H: i32, const L: i32> TryFrom<i32> for IntegerMinutes<BoundedInt32<H, L>> {
487    type Error = Error;
488    fn try_from(v: i32) -> Result<Self, Error> {
489        Ok(IntegerMinutes::new(v.try_into()?))
490    }
491}
492
493#[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
494/// This type represents an integer number of days.
495///
496/// The underlying type should usually implement `TryInto<u64>`.
497pub struct IntegerDays<T> {
498    /// Interior Value. Should Implement `TryInto<u64>` to be useful.
499    value: T,
500}
501
502impl<T> IntegerDays<T> {
503    /// Public Constructor
504    pub fn new(value: T) -> Self {
505        IntegerDays { value }
506    }
507
508    /// Deconstructor
509    ///
510    /// Use only in contexts where it's no longer possible to
511    /// use the Rust type system to ensure secs vs ms vs us correctness.
512    pub fn as_days(self) -> T {
513        self.value
514    }
515
516    /// Map the inner value (useful for conversion)
517    ///
518    /// ```
519    /// use tor_units::{BoundedInt32, IntegerDays};
520    ///
521    /// let value: IntegerDays<i32> = 42.into();
522    /// let value: IntegerDays<BoundedInt32<0,1000>>
523    ///     = value.try_map(TryInto::try_into).unwrap();
524    /// ```
525    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerDays<U>, E>
526    where
527        F: FnOnce(T) -> Result<U, E>,
528    {
529        Ok(IntegerDays::new(f(self.value)?))
530    }
531}
532
533impl<T: TryInto<u64>> TryFrom<IntegerDays<T>> for Duration {
534    type Error = Error;
535    fn try_from(val: IntegerDays<T>) -> Result<Self, Error> {
536        /// Number of seconds in a single day.
537        const SECONDS_PER_DAY: u64 = 86400;
538        let days: u64 = val.value.try_into().map_err(|_| Error::Overflow)?;
539        let seconds = days.checked_mul(SECONDS_PER_DAY).ok_or(Error::Overflow)?;
540        Ok(Self::from_secs(seconds))
541    }
542}
543
544impl<const H: i32, const L: i32> TryFrom<i32> for IntegerDays<BoundedInt32<H, L>> {
545    type Error = Error;
546    fn try_from(v: i32) -> Result<Self, Error> {
547        Ok(IntegerDays::new(v.try_into()?))
548    }
549}
550
551/// A SendMe Version
552///
553/// DOCDOC: Explain why this needs to have its own type, or remove it.
554#[derive(Clone, Copy, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
555pub struct SendMeVersion(u8);
556
557impl SendMeVersion {
558    /// Public Constructor
559    pub fn new(value: u8) -> Self {
560        SendMeVersion(value)
561    }
562
563    /// Helper
564    pub fn get(&self) -> u8 {
565        self.0
566    }
567}
568
569impl TryFrom<i32> for SendMeVersion {
570    type Error = Error;
571    fn try_from(v: i32) -> Result<Self, Error> {
572        let val_u8 = BoundedInt32::<0, 255>::checked_new(v)?;
573        Ok(SendMeVersion::new(val_u8.get() as u8))
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    #![allow(clippy::unwrap_used)]
580    use float_cmp::assert_approx_eq;
581
582    use super::*;
583
584    type TestFoo = BoundedInt32<1, 5>;
585    type TestBar = BoundedInt32<-45, 17>;
586
587    //make_parameter_type! {TestFoo(3,)}
588    #[test]
589    fn entire_range_parsed() {
590        let x: TestFoo = "1".parse().unwrap();
591        assert!(x.get() == 1);
592        let x: TestFoo = "2".parse().unwrap();
593        assert!(x.get() == 2);
594        let x: TestFoo = "3".parse().unwrap();
595        assert!(x.get() == 3);
596        let x: TestFoo = "4".parse().unwrap();
597        assert!(x.get() == 4);
598        let x: TestFoo = "5".parse().unwrap();
599        assert!(x.get() == 5);
600    }
601
602    #[test]
603    fn saturating() {
604        let x: TestFoo = TestFoo::saturating_new(1000);
605        let x_val: i32 = x.into();
606        assert!(x_val == TestFoo::UPPER);
607        let x: TestFoo = TestFoo::saturating_new(0);
608        let x_val: i32 = x.into();
609        assert!(x_val == TestFoo::LOWER);
610    }
611    #[test]
612    fn saturating_string() {
613        let x: TestFoo = TestFoo::saturating_from_str("1000").unwrap();
614        let x_val: i32 = x.into();
615        assert!(x_val == TestFoo::UPPER);
616        let x: TestFoo = TestFoo::saturating_from_str("0").unwrap();
617        let x_val: i32 = x.into();
618        assert!(x_val == TestFoo::LOWER);
619    }
620
621    #[test]
622    #[should_panic]
623    fn uninhabited_saturating_new() {
624        // This value should be uncreatable.
625        let _: BoundedInt32<10, 5> = BoundedInt32::saturating_new(7);
626    }
627
628    #[test]
629    fn uninhabited_from_string() {
630        let v: Result<BoundedInt32<10, 5>, Error> = BoundedInt32::saturating_from_str("7");
631        assert!(matches!(v, Err(Error::Uninhabited)));
632    }
633
634    #[test]
635    fn errors_correct() {
636        let x: Result<TestBar, Error> = "1000".parse();
637        assert!(x.unwrap_err() == Error::AboveUpperBound(1000, TestBar::UPPER));
638        let x: Result<TestBar, Error> = "-1000".parse();
639        assert!(x.unwrap_err() == Error::BelowLowerBound(-1000, TestBar::LOWER));
640        let x: Result<TestBar, Error> = "xyz".parse();
641        assert!(x.unwrap_err() == Error::Unrepresentable);
642    }
643
644    #[test]
645    fn display() {
646        let v = BoundedInt32::<99, 1000>::checked_new(345).unwrap();
647        assert_eq!(v.to_string(), "345".to_string());
648    }
649
650    #[test]
651    #[should_panic]
652    fn checked_too_high() {
653        let _: TestBar = "1000".parse().unwrap();
654    }
655
656    #[test]
657    #[should_panic]
658    fn checked_too_low() {
659        let _: TestBar = "-46".parse().unwrap();
660    }
661
662    #[test]
663    fn bounded_to_u64() {
664        let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
665        let u: u64 = b.try_into().unwrap();
666        assert_eq!(u, 77);
667
668        let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(-77).unwrap();
669        let u: Result<u64, Error> = b.try_into();
670        assert!(u.is_err());
671    }
672
673    #[test]
674    fn bounded_to_f64() {
675        let x: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
676        let f: f64 = x.into();
677        assert_approx_eq!(f64, f, 77.0);
678    }
679
680    #[test]
681    fn bounded_from_i32() {
682        let x: Result<BoundedInt32<-100, 100>, _> = 50.try_into();
683        let y: i32 = x.unwrap().into();
684        assert_eq!(y, 50);
685
686        let x: Result<BoundedInt32<-100, 100>, _> = 1000.try_into();
687        assert!(x.is_err());
688    }
689
690    #[test]
691    fn into_bool() {
692        let zero: BoundedInt32<0, 1> = BoundedInt32::saturating_from(0);
693        let one: BoundedInt32<0, 1> = BoundedInt32::saturating_from(1);
694
695        let f: bool = zero.into();
696        let t: bool = one.into();
697        assert!(!f);
698        assert!(t);
699    }
700
701    #[test]
702    fn into_u8() {
703        let zero: BoundedInt32<0, 255> = BoundedInt32::saturating_from(0);
704        let one: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1);
705        let ninety: BoundedInt32<0, 255> = BoundedInt32::saturating_from(90);
706        let max: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1000);
707
708        let a: u8 = zero.into();
709        let b: u8 = one.into();
710        let c: u8 = ninety.into();
711        let d: u8 = max.into();
712
713        assert_eq!(a, 0);
714        assert_eq!(b, 1);
715        assert_eq!(c, 90);
716        assert_eq!(d, 255);
717    }
718
719    #[test]
720    fn into_u32() {
721        let zero: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(0);
722        let one: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1);
723        let ninety: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(90);
724        let max: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1000);
725
726        assert_eq!(u32::from(zero), 0);
727        assert_eq!(u32::from(one), 1);
728        assert_eq!(u32::from(ninety), 90);
729        assert_eq!(u32::from(max), 1000);
730
731        let zero: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(0);
732        let one: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1);
733        let ninety: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(90);
734        let max: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1000);
735
736        assert_eq!(u32::from(zero), 1);
737        assert_eq!(u32::from(one), 1);
738        assert_eq!(u32::from(ninety), 90);
739        assert_eq!(u32::from(max), 1000);
740    }
741
742    #[test]
743    fn try_into_usize() {
744        let b0: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(0);
745        let b100: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(100);
746        let bn5: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(-5);
747        assert_eq!(usize::try_from(b0), Ok(0_usize));
748        assert_eq!(usize::try_from(b100), Ok(100_usize));
749        assert_eq!(usize::try_from(bn5), Err(Error::Negative));
750    }
751
752    #[test]
753    fn percents() {
754        type Pct = Percentage<u8>;
755        let p = Pct::new(100);
756        assert_eq!(p.as_percent(), 100);
757        assert_approx_eq!(f64, p.as_fraction(), 1.0);
758
759        let p = Pct::new(0);
760        assert_eq!(p.as_percent(), 0);
761        assert_approx_eq!(f64, p.as_fraction(), 0.0);
762
763        let p = Pct::new(25);
764        assert_eq!(p.as_percent(), 25);
765        assert_eq!(p.clone(), p);
766        assert_approx_eq!(f64, p.as_fraction(), 0.25);
767
768        type BPct = Percentage<BoundedInt32<0, 100>>;
769        assert_eq!(BPct::try_from(99).unwrap().as_percent().get(), 99);
770    }
771
772    #[test]
773    fn milliseconds() {
774        type Msec = IntegerMilliseconds<i32>;
775
776        let ms = Msec::new(500);
777        let d: Result<Duration, _> = ms.try_into();
778        assert_eq!(d.unwrap(), Duration::from_millis(500));
779        assert_eq!(Duration::try_from(ms * 2).unwrap(), Duration::from_secs(1));
780
781        let ms = Msec::new(-100);
782        let d: Result<Duration, _> = ms.try_into();
783        assert!(d.is_err());
784
785        type BMSec = IntegerMilliseconds<BoundedInt32<0, 1000>>;
786        let half_sec = BMSec::try_from(500).unwrap();
787        assert_eq!(
788            Duration::try_from(half_sec).unwrap(),
789            Duration::from_millis(500)
790        );
791        assert!(BMSec::try_from(1001).is_err());
792    }
793
794    #[test]
795    fn seconds() {
796        type Sec = IntegerSeconds<i32>;
797
798        let ms = Sec::new(500);
799        let d: Result<Duration, _> = ms.try_into();
800        assert_eq!(d.unwrap(), Duration::from_secs(500));
801
802        let ms = Sec::new(-100);
803        let d: Result<Duration, _> = ms.try_into();
804        assert!(d.is_err());
805
806        type BSec = IntegerSeconds<BoundedInt32<0, 3600>>;
807        let half_hour = BSec::try_from(1800).unwrap();
808        assert_eq!(
809            Duration::try_from(half_hour).unwrap(),
810            Duration::from_secs(1800)
811        );
812        assert!(BSec::try_from(9999).is_err());
813        assert_eq!(half_hour.clone(), half_hour);
814    }
815
816    #[test]
817    fn minutes() {
818        type Min = IntegerMinutes<i32>;
819
820        let t = Min::new(500);
821        let d: Duration = t.try_into().unwrap();
822        assert_eq!(d, Duration::from_secs(500 * 60));
823
824        let t = Min::new(-100);
825        let d: Result<Duration, _> = t.try_into();
826        assert_eq!(d, Err(Error::Overflow));
827
828        let t = IntegerMinutes::<u64>::new(u64::MAX);
829        let d: Result<Duration, _> = t.try_into();
830        assert_eq!(d, Err(Error::Overflow));
831
832        type BMin = IntegerMinutes<BoundedInt32<10, 30>>;
833        assert_eq!(
834            BMin::new(17_i32.try_into().unwrap()),
835            BMin::try_from(17).unwrap()
836        );
837    }
838
839    #[test]
840    fn days() {
841        type Days = IntegerDays<i32>;
842
843        let t = Days::new(500);
844        let d: Duration = t.try_into().unwrap();
845        assert_eq!(d, Duration::from_secs(500 * 86400));
846
847        let t = Days::new(-100);
848        let d: Result<Duration, _> = t.try_into();
849        assert_eq!(d, Err(Error::Overflow));
850
851        let t = IntegerDays::<u64>::new(u64::MAX);
852        let d: Result<Duration, _> = t.try_into();
853        assert_eq!(d, Err(Error::Overflow));
854
855        type BDays = IntegerDays<BoundedInt32<10, 30>>;
856        assert_eq!(
857            BDays::new(17_i32.try_into().unwrap()),
858            BDays::try_from(17).unwrap()
859        );
860    }
861
862    #[test]
863    fn sendme() {
864        let smv = SendMeVersion::new(5);
865        assert_eq!(smv.get(), 5);
866        assert_eq!(smv.clone().get(), 5);
867        assert_eq!(smv, SendMeVersion::try_from(5).unwrap());
868    }
869}