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