tor_units/
lib.rs

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