Skip to main content

tor_units/
lib.rs

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