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#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46
47use 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/// Conversion errors from converting a value into a [`BoundedInt32`].
57#[derive(Debug, Clone, PartialEq, Eq, Error)]
58#[non_exhaustive]
59pub enum Error {
60    /// A passed value was below the lower bound for the type.
61    #[error("Value {0} was below the lower bound {1} for this type")]
62    BelowLowerBound(i32, i32),
63    /// A passed value was above the upper bound for the type.
64    #[error("Value {0} was above the lower bound {1} for this type")]
65    AboveUpperBound(i32, i32),
66    /// Tried to convert a negative value to an unsigned type.
67    #[error("Tried to convert a negative value to an unsigned type")]
68    Negative,
69    /// Tried to parse a value that was not representable as the
70    /// underlying type.
71    #[error("Value could not be represented as an i32")]
72    Unrepresentable,
73    /// We encountered some kind of integer overflow when converting a number.
74    #[error("Integer overflow")]
75    Overflow,
76    /// Tried to instantiate an uninhabited type.
77    #[error("No value is valid for this type")]
78    Uninhabited,
79}
80
81/// A 32-bit signed integer with a restricted range.
82///
83/// This type holds an i32 value such that `LOWER` <= value <= `UPPER`
84///
85/// # Limitations
86///
87/// If you try to instantiate this type with LOWER > UPPER, you will
88/// get an uninhabitable type.  It would be better if we could check that at
89/// compile time, and prevent such types from being named.
90//
91// [TODO: If you need a Bounded* for some type other than i32, ask nickm:
92// he has an implementation kicking around.]
93#[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    /// Interior Value
101    value: i32,
102}
103
104impl<const LOWER: i32, const UPPER: i32> BoundedInt32<LOWER, UPPER> {
105    /// Lower bound
106    pub const LOWER: i32 = LOWER;
107    /// Upper bound
108    pub const UPPER: i32 = UPPER;
109
110    /// Private constructor function for this type.
111    fn unchecked_new(value: i32) -> Self {
112        assert!(LOWER <= UPPER); //The compiler optimizes this out, no run-time cost.
113
114        BoundedInt32 { value }
115    }
116
117    /// Return the lower bound value of this bounded i32.
118    ///
119    /// This always return [`Self::LOWER`].
120    pub const fn lower(&self) -> i32 {
121        LOWER
122    }
123
124    /// Return the lower bound value of this bounded i32.
125    ///
126    /// This always return [`Self::LOWER`].
127    pub const fn upper(&self) -> i32 {
128        UPPER
129    }
130
131    /// Return the underlying i32 value.
132    ///
133    /// This value will always be between [`Self::LOWER`] and [`Self::UPPER`],
134    /// inclusive.
135    pub fn get(&self) -> i32 {
136        self.value
137    }
138
139    /// If `val` is within range, return a new `BoundedInt32` wrapping
140    /// it; otherwise, clamp it to the upper or lower bound as
141    /// appropriate.
142    pub fn saturating_new(val: i32) -> Self {
143        Self::unchecked_new(Self::clamp(val))
144    }
145
146    /// If `val` is an acceptable value inside the range for this type,
147    /// return a new [`BoundedInt32`].  Otherwise return an error.
148    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    /// This private function clamps an input to the acceptable range.
159    fn clamp(val: i32) -> i32 {
160        Ord::clamp(val, LOWER, UPPER)
161    }
162
163    /// Convert from the underlying type, clamping to the upper or
164    /// lower bound if needed.
165    ///
166    /// # Panics
167    ///
168    /// This function will panic if UPPER < LOWER.
169    pub fn saturating_from(val: i32) -> Self {
170        Self::unchecked_new(Self::clamp(val))
171    }
172
173    /// Convert from a string, clamping to the upper or lower bound if needed.
174    ///
175    /// # Limitations
176    ///
177    /// If the input is a number that cannot be represented as an i32,
178    /// then we return an error instead of clamping it.
179    pub fn saturating_from_str(s: &str) -> Result<Self, Error> {
180        if UPPER < LOWER {
181            // The compiler should optimize this block out at compile time.
182            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/// A percentage value represented as a number.
262///
263/// This type wraps an underlying numeric type, and ensures that callers
264/// are clear whether they want a _fraction_, or a _percentage_.
265#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
266pub struct Percentage<T: Copy + Into<f64>> {
267    /// The underlying percentage value.
268    value: T,
269}
270
271impl<T: Copy + Into<f64>> Percentage<T> {
272    /// Create a new `IntPercentage` from the underlying percentage.
273    pub fn new(value: T) -> Self {
274        Self { value }
275    }
276
277    /// Return this value as a (possibly improper) fraction.
278    ///
279    /// ```
280    /// use tor_units::Percentage;
281    /// let pct_200 = Percentage::<u8>::new(200);
282    /// let pct_100 = Percentage::<u8>::new(100);
283    /// let pct_50 = Percentage::<u8>::new(50);
284    ///
285    /// assert_eq!(pct_200.as_fraction(), 2.0);
286    /// assert_eq!(pct_100.as_fraction(), 1.0);
287    /// assert_eq!(pct_50.as_fraction(), 0.5);
288    /// // Note: don't actually compare f64 with ==.
289    /// ```
290    pub fn as_fraction(self) -> f64 {
291        self.value.into() / 100.0
292    }
293
294    /// Return this value as a percentage.
295    ///
296    /// ```
297    /// use tor_units::Percentage;
298    /// let pct_200 = Percentage::<u8>::new(200);
299    /// let pct_100 = Percentage::<u8>::new(100);
300    /// let pct_50 = Percentage::<u8>::new(50);
301    ///
302    /// assert_eq!(pct_200.as_percent(), 200);
303    /// assert_eq!(pct_100.as_percent(), 100);
304    /// assert_eq!(pct_50.as_percent(), 50);
305    /// ```
306    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// TODO: There is a bunch of code duplication among these "IntegerTimeUnits"
319// section.
320
321#[derive(
322    Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash,
323)]
324/// This type represents an integer number of milliseconds.
325///
326/// The underlying type should usually implement `TryInto<u64>`.
327pub struct IntegerMilliseconds<T> {
328    /// Interior Value. Should implement `TryInto<u64>` to be useful.
329    value: T,
330}
331
332impl<T> IntegerMilliseconds<T> {
333    /// Public Constructor
334    pub fn new(value: T) -> Self {
335        IntegerMilliseconds { value }
336    }
337
338    /// Deconstructor
339    ///
340    /// Use only in contexts where it's no longer possible to
341    /// use the Rust type system to ensure secs vs ms vs us correctness.
342    pub fn as_millis(self) -> T {
343        self.value
344    }
345
346    /// Map the inner value (useful for conversion)
347    ///
348    /// # Example
349    ///
350    /// ```
351    /// use tor_units::{BoundedInt32, IntegerMilliseconds};
352    ///
353    /// let value: IntegerMilliseconds<i32> = 42.into();
354    /// let value: IntegerMilliseconds<BoundedInt32<0,1000>>
355    ///     = value.try_map(TryInto::try_into).unwrap();
356    /// ```
357    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)]
382/// This type represents an integer number of seconds.
383///
384/// The underlying type should usually implement `TryInto<u64>`.
385pub struct IntegerSeconds<T> {
386    /// Interior Value. Should implement `TryInto<u64>` to be useful.
387    value: T,
388}
389
390impl<T> IntegerSeconds<T> {
391    /// Public Constructor
392    pub fn new(value: T) -> Self {
393        IntegerSeconds { value }
394    }
395
396    /// Deconstructor
397    ///
398    /// Use only in contexts where it's no longer possible to
399    /// use the Rust type system to ensure secs vs ms vs us correctness.
400    pub fn as_secs(self) -> T {
401        self.value
402    }
403
404    /// Map the inner value (useful for conversion)
405    ///
406    /// ```
407    /// use tor_units::{BoundedInt32, IntegerSeconds};
408    ///
409    /// let value: IntegerSeconds<i32> = 42.into();
410    /// let value: IntegerSeconds<BoundedInt32<0,1000>>
411    ///     = value.try_map(TryInto::try_into).unwrap();
412    /// ```
413    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)] //
436#[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
437/// This type represents an integer number of minutes.
438///
439/// The underlying type should usually implement `TryInto<u64>`.
440pub struct IntegerMinutes<T> {
441    /// Interior Value. Should Implement `TryInto<u64>` to be useful.
442    value: T,
443}
444
445impl<T> IntegerMinutes<T> {
446    /// Public Constructor
447    pub fn new(value: T) -> Self {
448        IntegerMinutes { value }
449    }
450
451    /// Deconstructor
452    ///
453    /// Use only in contexts where it's no longer possible to
454    /// use the Rust type system to ensure secs vs ms vs us correctness.
455    pub fn as_minutes(self) -> T {
456        self.value
457    }
458
459    /// Map the inner value (useful for conversion)
460    ///
461    /// ```
462    /// use tor_units::{BoundedInt32, IntegerMinutes};
463    ///
464    /// let value: IntegerMinutes<i32> = 42.into();
465    /// let value: IntegerMinutes<BoundedInt32<0,1000>>
466    ///     = value.try_map(TryInto::try_into).unwrap();
467    /// ```
468    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        /// Number of seconds in a single minute.
480        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)]
497/// This type represents an integer number of days.
498///
499/// The underlying type should usually implement `TryInto<u64>`.
500pub struct IntegerDays<T> {
501    /// Interior Value. Should Implement `TryInto<u64>` to be useful.
502    value: T,
503}
504
505impl<T> IntegerDays<T> {
506    /// Public Constructor
507    pub fn new(value: T) -> Self {
508        IntegerDays { value }
509    }
510
511    /// Deconstructor
512    ///
513    /// Use only in contexts where it's no longer possible to
514    /// use the Rust type system to ensure secs vs ms vs us correctness.
515    pub fn as_days(self) -> T {
516        self.value
517    }
518
519    /// Map the inner value (useful for conversion)
520    ///
521    /// ```
522    /// use tor_units::{BoundedInt32, IntegerDays};
523    ///
524    /// let value: IntegerDays<i32> = 42.into();
525    /// let value: IntegerDays<BoundedInt32<0,1000>>
526    ///     = value.try_map(TryInto::try_into).unwrap();
527    /// ```
528    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        /// Number of seconds in a single day.
540        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/// A SendMe Version
555///
556/// DOCDOC: Explain why this needs to have its own type, or remove it.
557#[derive(Clone, Copy, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
558pub struct SendMeVersion(u8);
559
560impl SendMeVersion {
561    /// Public Constructor
562    pub fn new(value: u8) -> Self {
563        SendMeVersion(value)
564    }
565
566    /// Helper
567    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    //make_parameter_type! {TestFoo(3,)}
591    #[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        // This value should be uncreatable.
628        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}