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