1
1
//! `tor-units` -- Safe wrappers for primitive numeric types.
2
//!
3
//! # Overview
4
//!
5
//! This crate is part of
6
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
7
//! implement [Tor](https://www.torproject.org/) in Rust.
8
//! It provides safe wrappers for primitive numeric wrappers used in
9
//! other parts of Arti.
10
//! In particular, it provides:
11
//!   * a bounded i32 with both checked and clamping constructors,
12
//!   * an integer milliseconds wrapper with conversion to [`Duration`]
13
//!   * an integer seconds wrapper with conversion to [`Duration`]
14
//!   * a percentage wrapper, to prevent accidental failure
15
//!     to divide by 100.
16
//!   * a SendMeVersion which can be compared only.
17

            
18
#![deny(missing_docs)]
19
#![warn(noop_method_call)]
20
#![deny(unreachable_pub)]
21
#![warn(clippy::all)]
22
#![deny(clippy::await_holding_lock)]
23
#![deny(clippy::cargo_common_metadata)]
24
#![deny(clippy::cast_lossless)]
25
#![deny(clippy::checked_conversions)]
26
#![warn(clippy::cognitive_complexity)]
27
#![deny(clippy::debug_assert_with_mut_call)]
28
#![deny(clippy::exhaustive_enums)]
29
#![deny(clippy::exhaustive_structs)]
30
#![deny(clippy::expl_impl_clone_on_copy)]
31
#![deny(clippy::fallible_impl_from)]
32
#![deny(clippy::implicit_clone)]
33
#![deny(clippy::large_stack_arrays)]
34
#![warn(clippy::manual_ok_or)]
35
#![deny(clippy::missing_docs_in_private_items)]
36
#![deny(clippy::missing_panics_doc)]
37
#![warn(clippy::needless_borrow)]
38
#![warn(clippy::needless_pass_by_value)]
39
#![warn(clippy::option_option)]
40
#![warn(clippy::rc_buffer)]
41
#![deny(clippy::ref_option_ref)]
42
#![warn(clippy::semicolon_if_nothing_returned)]
43
#![warn(clippy::trait_duplication_in_bounds)]
44
#![deny(clippy::unnecessary_wraps)]
45
#![warn(clippy::unseparated_literal_suffix)]
46
#![deny(clippy::unwrap_used)]
47

            
48
use derive_more::{Add, Display, Div, From, FromStr, Mul};
49

            
50
use std::convert::{TryFrom, TryInto};
51
use std::time::Duration;
52
use thiserror::Error;
53

            
54
/// Conversion errors from converting a value into a [`BoundedInt32`].
55
6
#[derive(Debug, Clone, PartialEq, Eq, Error)]
56
#[non_exhaustive]
57
pub enum Error {
58
    /// A passed value was below the lower bound for the type.
59
    #[error("Value {0} was below the lower bound {1} for this type.")]
60
    BelowLowerBound(i32, i32),
61
    /// A passed value was above the upper bound for the type.
62
    #[error("Value {0} was above the lower bound {1} for this type.")]
63
    AboveUpperBound(i32, i32),
64
    /// Tried to convert a negative value to an unsigned type.
65
    #[error("Tried to convert a negative value to an unsigned type")]
66
    Negative,
67
    /// Tried to parse a value that was not representable as the
68
    /// underlying type.
69
    #[error("Value could not be represented as an i32")]
70
    Unrepresentable,
71
    /// We encountered some kind of integer overflow when converting a number.
72
    #[error("Integer overflow")]
73
    Overflow,
74
    /// Tried to instantiate an uninhabited type.
75
    #[error("No value is valid for this type")]
76
    Uninhabited,
77
}
78

            
79
/// A 32-bit signed integer with a restricted range.
80
///
81
/// This type holds an i32 value such that `LOWER` <= value <= `UPPER`
82
///
83
/// # Limitations
84
///
85
/// If you try to instantiate this type with LOWER > UPPER, you will
86
/// get an uninhabitable type.  It would be better if we could check that at
87
/// compile time, and prevent such types from being named.
88
//
89
// [TODO: If you need a Bounded* for some type other than i32, ask nickm:
90
// he has an implementation kicking around.]
91
2
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
92
pub struct BoundedInt32<const LOWER: i32, const UPPER: i32> {
93
    /// Interior Value
94
    value: i32,
95
}
96

            
97
impl<const LOWER: i32, const UPPER: i32> BoundedInt32<LOWER, UPPER> {
98
    /// Lower bound
99
    pub const LOWER: i32 = LOWER;
100
    /// Upper bound
101
    pub const UPPER: i32 = UPPER;
102

            
103
    /// Private constructor function for this type.
104
38228
    fn unchecked_new(value: i32) -> Self {
105
38228
        assert!(LOWER <= UPPER); //The compiler optimizes this out, no run-time cost.
106

            
107
38228
        BoundedInt32 { value }
108
38228
    }
109

            
110
    /// Return the underlying i32 value.
111
    ///
112
    /// This value will always be between [`Self::LOWER`] and [`Self::UPPER`],
113
    /// inclusive.
114
3097
    pub fn get(&self) -> i32 {
115
3097
        self.value
116
3097
    }
117

            
118
    /// If `val` is within range, return a new `BoundedInt32` wrapping
119
    /// it; otherwise, clamp it to the upper or lower bound as
120
    /// appropriate.
121
1232
    pub fn saturating_new(val: i32) -> Self {
122
1232
        Self::unchecked_new(Self::clamp(val))
123
1232
    }
124

            
125
    /// If `val` is an acceptable value inside the range for this type,
126
    /// return a new [`BoundedInt32`].  Otherwise return an error.
127
36985
    pub fn checked_new(val: i32) -> Result<Self, Error> {
128
36985
        if val > UPPER {
129
5
            Err(Error::AboveUpperBound(val, UPPER))
130
36980
        } else if val < LOWER {
131
2
            Err(Error::BelowLowerBound(val, LOWER))
132
        } else {
133
36978
            Ok(BoundedInt32::unchecked_new(val))
134
        }
135
36985
    }
136

            
137
    /// This private function clamps an input to the acceptable range.
138
1251
    fn clamp(val: i32) -> i32 {
139
1251
        Ord::clamp(val, LOWER, UPPER)
140
1251
    }
141

            
142
    /// Convert from the underlying type, clamping to the upper or
143
    /// lower bound if needed.
144
    ///
145
    /// # Panics
146
    ///
147
    /// This function will panic if UPPER < LOWER.
148
19
    pub fn saturating_from(val: i32) -> Self {
149
19
        Self::unchecked_new(Self::clamp(val))
150
19
    }
151

            
152
    /// Convert from a string, clamping to the upper or lower bound if needed.
153
    ///
154
    /// # Limitations
155
    ///
156
    /// If the input is a number that cannot be represented as an i32,
157
    /// then we return an error instead of clamping it.
158
3
    pub fn saturating_from_str(s: &str) -> Result<Self, Error> {
159
3
        if UPPER < LOWER {
160
            // The compiler should optimize this block out at compile time.
161
1
            return Err(Error::Uninhabited);
162
2
        }
163
2
        let val: i32 = s.parse().map_err(|_| Error::Unrepresentable)?;
164
2
        Ok(Self::saturating_from(val))
165
3
    }
166
}
167

            
168
impl<const L: i32, const U: i32> std::fmt::Display for BoundedInt32<L, U> {
169
1
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170
1
        write!(f, "{}", self.value)
171
1
    }
172
}
173

            
174
impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for i32 {
175
5
    fn from(val: BoundedInt32<L, U>) -> i32 {
176
5
        val.value
177
5
    }
178
}
179

            
180
impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for f64 {
181
12373
    fn from(val: BoundedInt32<L, U>) -> f64 {
182
12373
        val.value.into()
183
12373
    }
184
}
185

            
186
impl<const L: i32, const H: i32> TryFrom<i32> for BoundedInt32<L, H> {
187
    type Error = Error;
188
33862
    fn try_from(val: i32) -> Result<Self, Self::Error> {
189
33862
        Self::checked_new(val)
190
33862
    }
191
}
192

            
193
impl<const L: i32, const H: i32> std::str::FromStr for BoundedInt32<L, H> {
194
    type Err = Error;
195
10
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
196
10
        Self::checked_new(s.parse().map_err(|_| Error::Unrepresentable)?)
197
10
    }
198
}
199

            
200
impl From<BoundedInt32<0, 1>> for bool {
201
172
    fn from(val: BoundedInt32<0, 1>) -> bool {
202
172
        val.value == 1
203
172
    }
204
}
205

            
206
impl From<BoundedInt32<0, 255>> for u8 {
207
4
    fn from(val: BoundedInt32<0, 255>) -> u8 {
208
4
        val.value as u8
209
4
    }
210
}
211

            
212
impl<const H: i32> From<BoundedInt32<0, H>> for u32 {
213
4
    fn from(val: BoundedInt32<0, H>) -> u32 {
214
4
        val.value as u32
215
4
    }
216
}
217

            
218
impl<const H: i32> From<BoundedInt32<1, H>> for u32 {
219
741
    fn from(val: BoundedInt32<1, H>) -> u32 {
220
741
        val.value as u32
221
741
    }
222
}
223

            
224
impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for u64 {
225
    type Error = Error;
226
23253
    fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
227
23253
        if val.value < 0 {
228
1
            Err(Error::Negative)
229
        } else {
230
23252
            Ok(val.value as u64)
231
        }
232
23253
    }
233
}
234

            
235
impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for usize {
236
    type Error = Error;
237
19308
    fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
238
19308
        if val.value < 0 {
239
1
            Err(Error::Negative)
240
        } else {
241
19307
            Ok(val.value as usize)
242
        }
243
19308
    }
244
}
245

            
246
/// A percentage value represented as a number.
247
///
248
/// This type wraps an underlying numeric type, and ensures that callers
249
/// are clear whether they want a _fraction_, or a _percentage_.
250
1
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
251
pub struct Percentage<T: Copy + Into<f64>> {
252
    /// The underlying percentage value.
253
    value: T,
254
}
255

            
256
impl<T: Copy + Into<f64>> Percentage<T> {
257
    /// Create a new `IntPercentage` from the underlying percentage.
258
6331
    pub fn new(value: T) -> Self {
259
6331
        Self { value }
260
6331
    }
261

            
262
    /// Return this value as a (possibly improper) fraction.
263
    ///
264
    /// ```
265
    /// use tor_units::Percentage;
266
    /// let pct_200 = Percentage::<u8>::new(200);
267
    /// let pct_100 = Percentage::<u8>::new(100);
268
    /// let pct_50 = Percentage::<u8>::new(50);
269
    ///
270
    /// assert_eq!(pct_200.as_fraction(), 2.0);
271
    /// assert_eq!(pct_100.as_fraction(), 1.0);
272
    /// assert_eq!(pct_50.as_fraction(), 0.5);
273
    /// // Note: don't actually compare f64 with ==.
274
    /// ```
275
12375
    pub fn as_fraction(self) -> f64 {
276
12375
        self.value.into() / 100.0
277
12375
    }
278

            
279
    /// Return this value as a percentage.
280
    ///
281
    /// ```
282
    /// use tor_units::Percentage;
283
    /// let pct_200 = Percentage::<u8>::new(200);
284
    /// let pct_100 = Percentage::<u8>::new(100);
285
    /// let pct_50 = Percentage::<u8>::new(50);
286
    ///
287
    /// assert_eq!(pct_200.as_percent(), 200);
288
    /// assert_eq!(pct_100.as_percent(), 100);
289
    /// assert_eq!(pct_50.as_percent(), 50);
290
    /// ```
291
14
    pub fn as_percent(self) -> T {
292
14
        self.value
293
14
    }
294
}
295

            
296
impl<const H: i32, const L: i32> TryFrom<i32> for Percentage<BoundedInt32<H, L>> {
297
    type Error = Error;
298
6347
    fn try_from(v: i32) -> Result<Self, Error> {
299
6347
        Ok(Percentage::new(v.try_into()?))
300
6347
    }
301
}
302

            
303
#[derive(
304
1
    Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd,
305
)]
306
/// This type represents an integer number of milliseconds.
307
///
308
/// The underlying type should implement TryInto<u64>.
309
pub struct IntegerMilliseconds<T> {
310
    /// Interior Value. Should Implement TryInto<u64> to be useful.
311
    value: T,
312
}
313

            
314
impl<T: TryInto<u64>> IntegerMilliseconds<T> {
315
    /// Public Constructor
316
3188
    pub fn new(value: T) -> Self {
317
3188
        IntegerMilliseconds { value }
318
3188
    }
319
}
320

            
321
impl<T: TryInto<u64>> TryFrom<IntegerMilliseconds<T>> for Duration {
322
    type Error = <T as TryInto<u64>>::Error;
323
14
    fn try_from(val: IntegerMilliseconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
324
14
        Ok(Self::from_millis(val.value.try_into()?))
325
14
    }
326
}
327

            
328
impl<const H: i32, const L: i32> TryFrom<i32> for IntegerMilliseconds<BoundedInt32<H, L>> {
329
    type Error = Error;
330
3168
    fn try_from(v: i32) -> Result<Self, Error> {
331
3168
        Ok(IntegerMilliseconds::new(v.try_into()?))
332
3168
    }
333
}
334

            
335
#[derive(
336
1
    Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd,
337
)]
338
/// This type represents an integer number of seconds.
339
///
340
/// The underlying type should implement TryInto<u64>.
341
pub struct IntegerSeconds<T> {
342
    /// Interior Value. Should Implement TryInto<u64> to be useful.
343
    value: T,
344
}
345

            
346
impl<T: TryInto<u64>> IntegerSeconds<T> {
347
    /// Public Constructor
348
6356
    pub fn new(value: T) -> Self {
349
6356
        IntegerSeconds { value }
350
6356
    }
351
}
352

            
353
impl<T: TryInto<u64>> TryFrom<IntegerSeconds<T>> for Duration {
354
    type Error = <T as TryInto<u64>>::Error;
355
11656
    fn try_from(val: IntegerSeconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
356
11656
        Ok(Self::from_secs(val.value.try_into()?))
357
11656
    }
358
}
359

            
360
impl<const H: i32, const L: i32> TryFrom<i32> for IntegerSeconds<BoundedInt32<H, L>> {
361
    type Error = Error;
362
6350
    fn try_from(v: i32) -> Result<Self, Error> {
363
6350
        Ok(IntegerSeconds::new(v.try_into()?))
364
6350
    }
365
}
366

            
367
1
#[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd)]
368
/// This type represents an integer number of days.
369
///
370
/// The underlying type should implement TryInto<u64>.
371
pub struct IntegerDays<T> {
372
    /// Interior Value. Should Implement TryInto<u64> to be useful.
373
    value: T,
374
}
375

            
376
impl<T> IntegerDays<T> {
377
    /// Public Constructor
378
3174
    pub fn new(value: T) -> Self {
379
3174
        IntegerDays { value }
380
3174
    }
381
}
382

            
383
impl<T: TryInto<u64>> TryFrom<IntegerDays<T>> for Duration {
384
    type Error = Error;
385
11589
    fn try_from(val: IntegerDays<T>) -> Result<Self, Error> {
386
        /// Number of seconds in a single day.
387
        const SECONDS_PER_DAY: u64 = 86400;
388
11589
        let days: u64 = val.value.try_into().map_err(|_| Error::Overflow)?;
389
11588
        let seconds = days.checked_mul(SECONDS_PER_DAY).ok_or(Error::Overflow)?;
390
11587
        Ok(Self::from_secs(seconds))
391
11589
    }
392
}
393

            
394
impl<const H: i32, const L: i32> TryFrom<i32> for IntegerDays<BoundedInt32<H, L>> {
395
    type Error = Error;
396
3183
    fn try_from(v: i32) -> Result<Self, Error> {
397
3183
        Ok(IntegerDays::new(v.try_into()?))
398
3183
    }
399
}
400

            
401
/// A SendMe Version
402
///
403
/// DOCDOC: Explain why this needs to have its own type, or remove it.
404
1
#[derive(Clone, Copy, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd)]
405
pub struct SendMeVersion(u8);
406

            
407
impl SendMeVersion {
408
    /// Public Constructor
409
3130
    pub fn new(value: u8) -> Self {
410
3130
        SendMeVersion(value)
411
3130
    }
412

            
413
    /// Helper
414
70
    pub fn get(&self) -> u8 {
415
70
        self.0
416
70
    }
417
}
418

            
419
impl TryFrom<i32> for SendMeVersion {
420
    type Error = Error;
421
3027
    fn try_from(v: i32) -> Result<Self, Error> {
422
3027
        let val_u8 = BoundedInt32::<0, 255>::checked_new(v)?;
423
3027
        Ok(SendMeVersion::new(val_u8.get() as u8))
424
3027
    }
425
}
426

            
427
#[cfg(test)]
428
mod tests {
429
    #![allow(clippy::unwrap_used)]
430
    use float_cmp::assert_approx_eq;
431

            
432
    use super::*;
433
    use std::convert::TryInto;
434

            
435
    type TestFoo = BoundedInt32<1, 5>;
436
    type TestBar = BoundedInt32<-45, 17>;
437

            
438
    //make_parameter_type! {TestFoo(3,)}
439
    #[test]
440
    fn entire_range_parsed() {
441
        let x: TestFoo = "1".parse().unwrap();
442
        assert!(x.get() == 1);
443
        let x: TestFoo = "2".parse().unwrap();
444
        assert!(x.get() == 2);
445
        let x: TestFoo = "3".parse().unwrap();
446
        assert!(x.get() == 3);
447
        let x: TestFoo = "4".parse().unwrap();
448
        assert!(x.get() == 4);
449
        let x: TestFoo = "5".parse().unwrap();
450
        assert!(x.get() == 5);
451
    }
452

            
453
    #[test]
454
    fn saturating() {
455
        let x: TestFoo = TestFoo::saturating_new(1000);
456
        let x_val: i32 = x.into();
457
        assert!(x_val == TestFoo::UPPER);
458
        let x: TestFoo = TestFoo::saturating_new(0);
459
        let x_val: i32 = x.into();
460
        assert!(x_val == TestFoo::LOWER);
461
    }
462
    #[test]
463
    fn saturating_string() {
464
        let x: TestFoo = TestFoo::saturating_from_str("1000").unwrap();
465
        let x_val: i32 = x.into();
466
        assert!(x_val == TestFoo::UPPER);
467
        let x: TestFoo = TestFoo::saturating_from_str("0").unwrap();
468
        let x_val: i32 = x.into();
469
        assert!(x_val == TestFoo::LOWER);
470
    }
471

            
472
    #[test]
473
    #[should_panic]
474
    fn uninhabited_saturating_new() {
475
        // This value should be uncreatable.
476
        let _: BoundedInt32<10, 5> = BoundedInt32::saturating_new(7);
477
    }
478

            
479
    #[test]
480
    fn uninhabited_from_string() {
481
        let v: Result<BoundedInt32<10, 5>, Error> = BoundedInt32::saturating_from_str("7");
482
        assert!(matches!(v, Err(Error::Uninhabited)));
483
    }
484

            
485
    #[test]
486
    fn errors_correct() {
487
        let x: Result<TestBar, Error> = "1000".parse();
488
        assert!(x.unwrap_err() == Error::AboveUpperBound(1000, TestBar::UPPER));
489
        let x: Result<TestBar, Error> = "-1000".parse();
490
        assert!(x.unwrap_err() == Error::BelowLowerBound(-1000, TestBar::LOWER));
491
        let x: Result<TestBar, Error> = "xyz".parse();
492
        assert!(x.unwrap_err() == Error::Unrepresentable);
493
    }
494

            
495
    #[test]
496
    fn display() {
497
        let v = BoundedInt32::<99, 1000>::checked_new(345).unwrap();
498
        assert_eq!(v.to_string(), "345".to_string());
499
    }
500

            
501
    #[test]
502
    #[should_panic]
503
    fn checked_too_high() {
504
        let _: TestBar = "1000".parse().unwrap();
505
    }
506

            
507
    #[test]
508
    #[should_panic]
509
    fn checked_too_low() {
510
        let _: TestBar = "-46".parse().unwrap();
511
    }
512

            
513
    #[test]
514
    fn bounded_to_u64() {
515
        let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
516
        let u: u64 = b.try_into().unwrap();
517
        assert_eq!(u, 77);
518

            
519
        let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(-77).unwrap();
520
        let u: Result<u64, Error> = b.try_into();
521
        assert!(u.is_err());
522
    }
523

            
524
    #[test]
525
    fn bounded_to_f64() {
526
        let x: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
527
        let f: f64 = x.into();
528
        assert_approx_eq!(f64, f, 77.0);
529
    }
530

            
531
    #[test]
532
    fn bounded_from_i32() {
533
        let x: Result<BoundedInt32<-100, 100>, _> = 50.try_into();
534
        let y: i32 = x.unwrap().into();
535
        assert_eq!(y, 50);
536

            
537
        let x: Result<BoundedInt32<-100, 100>, _> = 1000.try_into();
538
        assert!(x.is_err());
539
    }
540

            
541
    #[test]
542
    fn into_bool() {
543
        let zero: BoundedInt32<0, 1> = BoundedInt32::saturating_from(0);
544
        let one: BoundedInt32<0, 1> = BoundedInt32::saturating_from(1);
545

            
546
        let f: bool = zero.into();
547
        let t: bool = one.into();
548
        assert!(!f);
549
        assert!(t);
550
    }
551

            
552
    #[test]
553
    fn into_u8() {
554
        let zero: BoundedInt32<0, 255> = BoundedInt32::saturating_from(0);
555
        let one: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1);
556
        let ninety: BoundedInt32<0, 255> = BoundedInt32::saturating_from(90);
557
        let max: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1000);
558

            
559
        let a: u8 = zero.into();
560
        let b: u8 = one.into();
561
        let c: u8 = ninety.into();
562
        let d: u8 = max.into();
563

            
564
        assert_eq!(a, 0);
565
        assert_eq!(b, 1);
566
        assert_eq!(c, 90);
567
        assert_eq!(d, 255);
568
    }
569

            
570
    #[test]
571
    fn into_u32() {
572
        let zero: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(0);
573
        let one: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1);
574
        let ninety: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(90);
575
        let max: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1000);
576

            
577
        assert_eq!(u32::from(zero), 0);
578
        assert_eq!(u32::from(one), 1);
579
        assert_eq!(u32::from(ninety), 90);
580
        assert_eq!(u32::from(max), 1000);
581

            
582
        let zero: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(0);
583
        let one: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1);
584
        let ninety: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(90);
585
        let max: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1000);
586

            
587
        assert_eq!(u32::from(zero), 1);
588
        assert_eq!(u32::from(one), 1);
589
        assert_eq!(u32::from(ninety), 90);
590
        assert_eq!(u32::from(max), 1000);
591
    }
592

            
593
    #[test]
594
    fn try_into_usize() {
595
        let b0: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(0);
596
        let b100: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(100);
597
        let bn5: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(-5);
598
        assert_eq!(usize::try_from(b0), Ok(0_usize));
599
        assert_eq!(usize::try_from(b100), Ok(100_usize));
600
        assert_eq!(usize::try_from(bn5), Err(Error::Negative));
601
    }
602

            
603
    #[test]
604
    fn percents() {
605
        type Pct = Percentage<u8>;
606
        let p = Pct::new(100);
607
        assert_eq!(p.as_percent(), 100);
608
        assert_approx_eq!(f64, p.as_fraction(), 1.0);
609

            
610
        let p = Pct::new(0);
611
        assert_eq!(p.as_percent(), 0);
612
        assert_approx_eq!(f64, p.as_fraction(), 0.0);
613

            
614
        let p = Pct::new(25);
615
        assert_eq!(p.as_percent(), 25);
616
        assert_eq!(p.clone(), p);
617
        assert_approx_eq!(f64, p.as_fraction(), 0.25);
618

            
619
        type BPct = Percentage<BoundedInt32<0, 100>>;
620
        assert_eq!(BPct::try_from(99).unwrap().as_percent().get(), 99);
621
    }
622

            
623
    #[test]
624
    fn milliseconds() {
625
        type Msec = IntegerMilliseconds<i32>;
626

            
627
        let ms = Msec::new(500);
628
        let d: Result<Duration, _> = ms.try_into();
629
        assert_eq!(d.unwrap(), Duration::from_millis(500));
630
        assert_eq!(Duration::try_from(ms * 2).unwrap(), Duration::from_secs(1));
631

            
632
        let ms = Msec::new(-100);
633
        let d: Result<Duration, _> = ms.try_into();
634
        assert!(d.is_err());
635

            
636
        type BMSec = IntegerMilliseconds<BoundedInt32<0, 1000>>;
637
        let half_sec = BMSec::try_from(500).unwrap();
638
        assert_eq!(
639
            Duration::try_from(half_sec).unwrap(),
640
            Duration::from_millis(500)
641
        );
642
        assert!(BMSec::try_from(1001).is_err());
643
    }
644

            
645
    #[test]
646
    fn seconds() {
647
        type Sec = IntegerSeconds<i32>;
648

            
649
        let ms = Sec::new(500);
650
        let d: Result<Duration, _> = ms.try_into();
651
        assert_eq!(d.unwrap(), Duration::from_secs(500));
652

            
653
        let ms = Sec::new(-100);
654
        let d: Result<Duration, _> = ms.try_into();
655
        assert!(d.is_err());
656

            
657
        type BSec = IntegerSeconds<BoundedInt32<0, 3600>>;
658
        let half_hour = BSec::try_from(1800).unwrap();
659
        assert_eq!(
660
            Duration::try_from(half_hour).unwrap(),
661
            Duration::from_secs(1800)
662
        );
663
        assert!(BSec::try_from(9999).is_err());
664
        assert_eq!(half_hour.clone(), half_hour);
665
    }
666

            
667
    #[test]
668
    fn days() {
669
        type Days = IntegerDays<i32>;
670

            
671
        let t = Days::new(500);
672
        let d: Duration = t.try_into().unwrap();
673
        assert_eq!(d, Duration::from_secs(500 * 86400));
674

            
675
        let t = Days::new(-100);
676
        let d: Result<Duration, _> = t.try_into();
677
        assert_eq!(d, Err(Error::Overflow));
678

            
679
        let t = IntegerDays::<u64>::new(u64::MAX);
680
        let d: Result<Duration, _> = t.try_into();
681
        assert_eq!(d, Err(Error::Overflow));
682

            
683
        type BDays = IntegerDays<BoundedInt32<10, 30>>;
684
        assert_eq!(
685
            BDays::new(17_i32.try_into().unwrap()),
686
            BDays::try_from(17).unwrap()
687
        );
688
    }
689

            
690
    #[test]
691
    fn sendme() {
692
        let smv = SendMeVersion::new(5);
693
        assert_eq!(smv.get(), 5);
694
        assert_eq!(smv.clone().get(), 5);
695
        assert_eq!(smv, SendMeVersion::try_from(5).unwrap());
696
    }
697
}