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