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
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

            
52
use derive_more::{Add, Display, Div, From, FromStr, Mul};
53

            
54
use serde::{Deserialize, Serialize};
55
use std::time::Duration;
56
use thiserror::Error;
57

            
58
#[cfg(feature = "memquota-memcost")]
59
use {derive_deftly::Deftly, tor_memquota::derive_deftly_template_HasMemoryCost};
60

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

            
83
/// A 32-bit signed integer with a restricted range.
84
///
85
/// This type holds an i32 value such that `LOWER` <= value <= `UPPER`
86
///
87
/// # Limitations
88
///
89
/// If you were to try to instantiate this type with LOWER > UPPER,
90
/// you would get an uninhabitable type.
91
/// Attempting to construct a value with a type with LOWER > UPPER
92
/// will result in a compile-time error;
93
/// though there may not be a compiler error if the code that constructs the value is
94
/// dead code and is optimized away.
95
/// It would be better if we could prevent such types from being named.
96
//
97
// [TODO: If you need a Bounded* for some type other than i32, ask nickm:
98
// he has an implementation kicking around.]
99
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
100
#[cfg_attr(
101
    feature = "memquota-memcost",
102
    derive(Deftly),
103
    derive_deftly(HasMemoryCost)
104
)]
105
pub struct BoundedInt32<const LOWER: i32, const UPPER: i32> {
106
    /// Interior Value
107
    value: i32,
108
}
109

            
110
impl<const LOWER: i32, const UPPER: i32> BoundedInt32<LOWER, UPPER> {
111
    /// Lower bound
112
    pub const LOWER: i32 = LOWER;
113
    /// Upper bound
114
    pub const UPPER: i32 = UPPER;
115

            
116
    /// Private constructor function for this type.
117
1740430
    fn unchecked_new(value: i32) -> Self {
118
        // If there is a code path leading to this function that remains after dead code elimination,
119
        // this will ensures LOWER <= UPPER at build time.
120
        const { assert!(LOWER <= UPPER) };
121

            
122
1740430
        BoundedInt32 { value }
123
1740430
    }
124

            
125
    /// Return the lower bound value of this bounded i32.
126
    ///
127
    /// This always return [`Self::LOWER`].
128
97
    pub const fn lower(&self) -> i32 {
129
97
        LOWER
130
97
    }
131

            
132
    /// Return the lower bound value of this bounded i32.
133
    ///
134
    /// This always return [`Self::LOWER`].
135
97
    pub const fn upper(&self) -> i32 {
136
97
        UPPER
137
97
    }
138

            
139
    /// Return the underlying i32 value.
140
    ///
141
    /// This value will always be between [`Self::LOWER`] and [`Self::UPPER`],
142
    /// inclusive.
143
45900
    pub fn get(&self) -> i32 {
144
45900
        self.value
145
45900
    }
146

            
147
    /// Return the underlying u32 value, if [`Self::LOWER`] is non-negative.
148
    ///
149
    /// If [`Self::LOWER`] is negative, this will panic at build-time.
150
    ///
151
    /// This value will always be between [`Self::LOWER`] and [`Self::UPPER`],
152
    /// inclusive.
153
19628
    pub fn get_u32(&self) -> u32 {
154
        const { assert!(LOWER >= 0) };
155
19628
        self.value as u32
156
19628
    }
157

            
158
    /// If `val` is within range, return a new `BoundedInt32` wrapping
159
    /// it; otherwise, clamp it to the upper or lower bound as
160
    /// appropriate.
161
52
    pub fn saturating_new(val: i32) -> Self {
162
52
        Self::unchecked_new(Self::clamp(val))
163
52
    }
164

            
165
    /// If `val` is an acceptable value inside the range for this type,
166
    /// return a new [`BoundedInt32`].  Otherwise return an error.
167
1740416
    pub fn checked_new(val: i32) -> Result<Self, Error> {
168
1740416
        if val > UPPER {
169
55
            Err(Error::AboveUpperBound(val, UPPER))
170
1740361
        } else if val < LOWER {
171
2
            Err(Error::BelowLowerBound(val, LOWER))
172
        } else {
173
1740359
            Ok(BoundedInt32::unchecked_new(val))
174
        }
175
1740416
    }
176

            
177
    /// This private function clamps an input to the acceptable range.
178
71
    fn clamp(val: i32) -> i32 {
179
71
        Ord::clamp(val, LOWER, UPPER)
180
71
    }
181

            
182
    /// Convert from the underlying type, clamping to the upper or
183
    /// lower bound if needed.
184
    ///
185
    /// # Panics
186
    ///
187
    /// This function will panic if UPPER < LOWER.
188
19
    pub fn saturating_from(val: i32) -> Self {
189
19
        Self::unchecked_new(Self::clamp(val))
190
19
    }
191

            
192
    /// Convert from a string, clamping to the upper or lower bound if needed.
193
    ///
194
    /// # Limitations
195
    ///
196
    /// If the input is a number that cannot be represented as an i32,
197
    /// then we return an error instead of clamping it.
198
2
    pub fn saturating_from_str(s: &str) -> Result<Self, Error> {
199
2
        let val: i32 = s.parse().map_err(|_| Error::Unrepresentable)?;
200
2
        Ok(Self::saturating_from(val))
201
2
    }
202
}
203

            
204
impl<const L: i32, const U: i32> std::fmt::Display for BoundedInt32<L, U> {
205
1
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206
1
        write!(f, "{}", self.value)
207
1
    }
208
}
209

            
210
impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for i32 {
211
5
    fn from(val: BoundedInt32<L, U>) -> i32 {
212
5
        val.value
213
5
    }
214
}
215

            
216
impl<const L: i32, const U: i32> From<BoundedInt32<L, U>> for f64 {
217
15548
    fn from(val: BoundedInt32<L, U>) -> f64 {
218
15548
        val.value.into()
219
15548
    }
220
}
221

            
222
impl<const L: i32, const H: i32> TryFrom<i32> for BoundedInt32<L, H> {
223
    type Error = Error;
224
1634011
    fn try_from(val: i32) -> Result<Self, Self::Error> {
225
1634011
        Self::checked_new(val)
226
1634011
    }
227
}
228

            
229
impl<const L: i32, const H: i32> std::str::FromStr for BoundedInt32<L, H> {
230
    type Err = Error;
231
10
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
232
10
        Self::checked_new(s.parse().map_err(|_| Error::Unrepresentable)?)
233
10
    }
234
}
235

            
236
impl From<BoundedInt32<0, 1>> for bool {
237
1187330
    fn from(val: BoundedInt32<0, 1>) -> bool {
238
1187330
        val.value == 1
239
1187330
    }
240
}
241

            
242
impl From<BoundedInt32<0, 255>> for u8 {
243
4
    fn from(val: BoundedInt32<0, 255>) -> u8 {
244
4
        val.value as u8
245
4
    }
246
}
247

            
248
impl From<BoundedInt32<1, 254>> for u8 {
249
4416
    fn from(val: BoundedInt32<1, 254>) -> u8 {
250
4416
        val.value as u8
251
4416
    }
252
}
253

            
254
/// Convert to `u32`.
255
///
256
/// If `L` is negative, this will panic at build-time. See [`BoundedInt32::get_u32`].
257
impl<const L: i32, const H: i32> From<BoundedInt32<L, H>> for u32 {
258
19143
    fn from(val: BoundedInt32<L, H>) -> u32 {
259
19143
        val.get_u32()
260
19143
    }
261
}
262

            
263
impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for u64 {
264
    type Error = Error;
265
42086
    fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
266
42086
        if val.value < 0 {
267
1
            Err(Error::Negative)
268
        } else {
269
42085
            Ok(val.value as u64)
270
        }
271
42086
    }
272
}
273

            
274
impl<const L: i32, const H: i32> TryFrom<BoundedInt32<L, H>> for usize {
275
    type Error = Error;
276
14874
    fn try_from(val: BoundedInt32<L, H>) -> Result<Self, Self::Error> {
277
14874
        if val.value < 0 {
278
1
            Err(Error::Negative)
279
        } else {
280
14873
            Ok(val.value as usize)
281
        }
282
14874
    }
283
}
284

            
285
/// A percentage value represented as a number.
286
///
287
/// This type wraps an underlying numeric type, and ensures that callers
288
/// are clear whether they want a _fraction_, or a _percentage_.
289
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
290
pub struct Percentage<T: Copy + Into<f64>> {
291
    /// The underlying percentage value.
292
    value: T,
293
}
294

            
295
impl<T: Copy + Into<f64>> Percentage<T> {
296
    /// Create a new `IntPercentage` from the underlying percentage.
297
182229
    pub fn new(value: T) -> Self {
298
182229
        Self { value }
299
182229
    }
300

            
301
    /// Return this value as a (possibly improper) fraction.
302
    ///
303
    /// ```
304
    /// use tor_units::Percentage;
305
    /// let pct_200 = Percentage::<u8>::new(200);
306
    /// let pct_100 = Percentage::<u8>::new(100);
307
    /// let pct_50 = Percentage::<u8>::new(50);
308
    ///
309
    /// assert_eq!(pct_200.as_fraction(), 2.0);
310
    /// assert_eq!(pct_100.as_fraction(), 1.0);
311
    /// assert_eq!(pct_50.as_fraction(), 0.5);
312
    /// // Note: don't actually compare f64 with ==.
313
    /// ```
314
15550
    pub fn as_fraction(self) -> f64 {
315
15550
        self.value.into() / 100.0
316
15550
    }
317

            
318
    /// Return this value as a percentage.
319
    ///
320
    /// ```
321
    /// use tor_units::Percentage;
322
    /// let pct_200 = Percentage::<u8>::new(200);
323
    /// let pct_100 = Percentage::<u8>::new(100);
324
    /// let pct_50 = Percentage::<u8>::new(50);
325
    ///
326
    /// assert_eq!(pct_200.as_percent(), 200);
327
    /// assert_eq!(pct_100.as_percent(), 100);
328
    /// assert_eq!(pct_50.as_percent(), 50);
329
    /// ```
330
549
    pub fn as_percent(self) -> T {
331
549
        self.value
332
549
    }
333
}
334

            
335
impl<const H: i32, const L: i32> TryFrom<i32> for Percentage<BoundedInt32<H, L>> {
336
    type Error = Error;
337
179741
    fn try_from(v: i32) -> Result<Self, Error> {
338
179741
        Ok(Percentage::new(v.try_into()?))
339
179741
    }
340
}
341

            
342
// TODO: There is a bunch of code duplication among these "IntegerTimeUnits"
343
// section.
344

            
345
#[derive(
346
    Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash,
347
)]
348
/// This type represents an integer number of milliseconds.
349
///
350
/// The underlying type should usually implement `TryInto<u64>`.
351
pub struct IntegerMilliseconds<T> {
352
    /// Interior Value. Should implement `TryInto<u64>` to be useful.
353
    value: T,
354
}
355

            
356
impl<T> IntegerMilliseconds<T> {
357
    /// Public Constructor
358
120588
    pub fn new(value: T) -> Self {
359
120588
        IntegerMilliseconds { value }
360
120588
    }
361

            
362
    /// Deconstructor
363
    ///
364
    /// Use only in contexts where it's no longer possible to
365
    /// use the Rust type system to ensure secs vs ms vs us correctness.
366
9958
    pub fn as_millis(self) -> T {
367
9958
        self.value
368
9958
    }
369

            
370
    /// Map the inner value (useful for conversion)
371
    ///
372
    /// # Example
373
    ///
374
    /// ```
375
    /// use tor_units::{BoundedInt32, IntegerMilliseconds};
376
    ///
377
    /// let value: IntegerMilliseconds<i32> = 42.into();
378
    /// let value: IntegerMilliseconds<BoundedInt32<0,1000>>
379
    ///     = value.try_map(TryInto::try_into).unwrap();
380
    /// ```
381
3466
    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerMilliseconds<U>, E>
382
3466
    where
383
3466
        F: FnOnce(T) -> Result<U, E>,
384
    {
385
3466
        Ok(IntegerMilliseconds::new(f(self.value)?))
386
3466
    }
387
}
388

            
389
impl<T: TryInto<u64>> TryFrom<IntegerMilliseconds<T>> for Duration {
390
    type Error = <T as TryInto<u64>>::Error;
391
16
    fn try_from(val: IntegerMilliseconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
392
16
        Ok(Self::from_millis(val.value.try_into()?))
393
16
    }
394
}
395

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

            
403
#[derive(
404
    Add, Copy, Clone, Mul, Div, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash,
405
)]
406
/// This type represents an integer number of seconds.
407
///
408
/// The underlying type should usually implement `TryInto<u64>`.
409
pub struct IntegerSeconds<T> {
410
    /// Interior Value. Should implement `TryInto<u64>` to be useful.
411
    value: T,
412
}
413

            
414
impl<T> IntegerSeconds<T> {
415
    /// Public Constructor
416
212523
    pub fn new(value: T) -> Self {
417
212523
        IntegerSeconds { value }
418
212523
    }
419

            
420
    /// Deconstructor
421
    ///
422
    /// Use only in contexts where it's no longer possible to
423
    /// use the Rust type system to ensure secs vs ms vs us correctness.
424
    pub fn as_secs(self) -> T {
425
        self.value
426
    }
427

            
428
    /// Map the inner value (useful for conversion)
429
    ///
430
    /// ```
431
    /// use tor_units::{BoundedInt32, IntegerSeconds};
432
    ///
433
    /// let value: IntegerSeconds<i32> = 42.into();
434
    /// let value: IntegerSeconds<BoundedInt32<0,1000>>
435
    ///     = value.try_map(TryInto::try_into).unwrap();
436
    /// ```
437
    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerSeconds<U>, E>
438
    where
439
        F: FnOnce(T) -> Result<U, E>,
440
    {
441
        Ok(IntegerSeconds::new(f(self.value)?))
442
    }
443
}
444

            
445
impl<T: TryInto<u64>> TryFrom<IntegerSeconds<T>> for Duration {
446
    type Error = <T as TryInto<u64>>::Error;
447
16942
    fn try_from(val: IntegerSeconds<T>) -> Result<Self, <T as TryInto<u64>>::Error> {
448
16942
        Ok(Self::from_secs(val.value.try_into()?))
449
16942
    }
450
}
451

            
452
impl<const H: i32, const L: i32> TryFrom<i32> for IntegerSeconds<BoundedInt32<H, L>> {
453
    type Error = Error;
454
212422
    fn try_from(v: i32) -> Result<Self, Error> {
455
212422
        Ok(IntegerSeconds::new(v.try_into()?))
456
212422
    }
457
}
458

            
459
#[derive(Deserialize, Serialize)] //
460
#[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
461
/// This type represents an integer number of minutes.
462
///
463
/// The underlying type should usually implement `TryInto<u64>`.
464
pub struct IntegerMinutes<T> {
465
    /// Interior Value. Should Implement `TryInto<u64>` to be useful.
466
    value: T,
467
}
468

            
469
impl<T> IntegerMinutes<T> {
470
    /// Public Constructor
471
46911
    pub fn new(value: T) -> Self {
472
46911
        IntegerMinutes { value }
473
46911
    }
474

            
475
    /// Deconstructor
476
    ///
477
    /// Use only in contexts where it's no longer possible to
478
    /// use the Rust type system to ensure secs vs ms vs us correctness.
479
293322
    pub fn as_minutes(self) -> T {
480
293322
        self.value
481
293322
    }
482

            
483
    /// Map the inner value (useful for conversion)
484
    ///
485
    /// ```
486
    /// use tor_units::{BoundedInt32, IntegerMinutes};
487
    ///
488
    /// let value: IntegerMinutes<i32> = 42.into();
489
    /// let value: IntegerMinutes<BoundedInt32<0,1000>>
490
    ///     = value.try_map(TryInto::try_into).unwrap();
491
    /// ```
492
    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerMinutes<U>, E>
493
    where
494
        F: FnOnce(T) -> Result<U, E>,
495
    {
496
        Ok(IntegerMinutes::new(f(self.value)?))
497
    }
498
}
499

            
500
impl<T: TryInto<u64>> TryFrom<IntegerMinutes<T>> for Duration {
501
    type Error = Error;
502
19498
    fn try_from(val: IntegerMinutes<T>) -> Result<Self, Error> {
503
        /// Number of seconds in a single minute.
504
        const SECONDS_PER_MINUTE: u64 = 60;
505
19498
        let minutes: u64 = val.value.try_into().map_err(|_| Error::Overflow)?;
506
19497
        let seconds = minutes
507
19497
            .checked_mul(SECONDS_PER_MINUTE)
508
19497
            .ok_or(Error::Overflow)?;
509
19496
        Ok(Self::from_secs(seconds))
510
19498
    }
511
}
512

            
513
impl<const H: i32, const L: i32> TryFrom<i32> for IntegerMinutes<BoundedInt32<H, L>> {
514
    type Error = Error;
515
16341
    fn try_from(v: i32) -> Result<Self, Error> {
516
16341
        Ok(IntegerMinutes::new(v.try_into()?))
517
16341
    }
518
}
519

            
520
#[derive(Copy, Clone, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
521
/// This type represents an integer number of days.
522
///
523
/// The underlying type should usually implement `TryInto<u64>`.
524
pub struct IntegerDays<T> {
525
    /// Interior Value. Should Implement `TryInto<u64>` to be useful.
526
    value: T,
527
}
528

            
529
impl<T> IntegerDays<T> {
530
    /// Public Constructor
531
49028
    pub fn new(value: T) -> Self {
532
49028
        IntegerDays { value }
533
49028
    }
534

            
535
    /// Deconstructor
536
    ///
537
    /// Use only in contexts where it's no longer possible to
538
    /// use the Rust type system to ensure secs vs ms vs us correctness.
539
    pub fn as_days(self) -> T {
540
        self.value
541
    }
542

            
543
    /// Map the inner value (useful for conversion)
544
    ///
545
    /// ```
546
    /// use tor_units::{BoundedInt32, IntegerDays};
547
    ///
548
    /// let value: IntegerDays<i32> = 42.into();
549
    /// let value: IntegerDays<BoundedInt32<0,1000>>
550
    ///     = value.try_map(TryInto::try_into).unwrap();
551
    /// ```
552
    pub fn try_map<U, F, E>(self, f: F) -> Result<IntegerDays<U>, E>
553
    where
554
        F: FnOnce(T) -> Result<U, E>,
555
    {
556
        Ok(IntegerDays::new(f(self.value)?))
557
    }
558
}
559

            
560
impl<T: TryInto<u64>> TryFrom<IntegerDays<T>> for Duration {
561
    type Error = Error;
562
5643
    fn try_from(val: IntegerDays<T>) -> Result<Self, Error> {
563
        /// Number of seconds in a single day.
564
        const SECONDS_PER_DAY: u64 = 86400;
565
5643
        let days: u64 = val.value.try_into().map_err(|_| Error::Overflow)?;
566
5642
        let seconds = days.checked_mul(SECONDS_PER_DAY).ok_or(Error::Overflow)?;
567
5641
        Ok(Self::from_secs(seconds))
568
5643
    }
569
}
570

            
571
impl<const H: i32, const L: i32> TryFrom<i32> for IntegerDays<BoundedInt32<H, L>> {
572
    type Error = Error;
573
49021
    fn try_from(v: i32) -> Result<Self, Error> {
574
49021
        Ok(IntegerDays::new(v.try_into()?))
575
49021
    }
576
}
577

            
578
/// A SendMe Version
579
///
580
/// DOCDOC: Explain why this needs to have its own type, or remove it.
581
#[derive(Clone, Copy, From, FromStr, Display, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
582
pub struct SendMeVersion(u8);
583

            
584
impl SendMeVersion {
585
    /// Public Constructor
586
36530
    pub fn new(value: u8) -> Self {
587
36530
        SendMeVersion(value)
588
36530
    }
589

            
590
    /// Helper
591
242
    pub fn get(&self) -> u8 {
592
242
        self.0
593
242
    }
594
}
595

            
596
impl TryFrom<i32> for SendMeVersion {
597
    type Error = Error;
598
36289
    fn try_from(v: i32) -> Result<Self, Error> {
599
36289
        let val_u8 = BoundedInt32::<0, 255>::checked_new(v)?;
600
36289
        Ok(SendMeVersion::new(val_u8.get() as u8))
601
36289
    }
602
}
603

            
604
/// Tests that check whether some code fails to compile as intended.
605
// Unfortunately we can't check the reason that it fails to compile,
606
// so these tests could become stale if the API is changed.
607
// In the future, we may be able to use the (currently nightly):
608
// https://doc.rust-lang.org/rustdoc/unstable-features.html?highlight=compile_fail#error-numbers-for-compile-fail-doctests
609
#[cfg(doc)]
610
#[doc(hidden)]
611
mod compile_fail_tests {
612
    /// ```compile_fail
613
    /// use tor_units::BoundedInt32;
614
    /// let _: BoundedInt32<10, 5> = BoundedInt32::saturating_new(7);
615
    /// ```
616
    fn uninhabited_saturating_new() {}
617

            
618
    /// ```compile_fail
619
    /// use tor_units::BoundedInt32;
620
    /// let _: Result<BoundedInt32<10, 5>, Error> = BoundedInt32::saturating_from_str("7");
621
    /// ```
622
    fn uninhabited_from_string() {}
623
}
624

            
625
#[cfg(test)]
626
mod tests {
627
    #![allow(clippy::unwrap_used)]
628
    use float_cmp::assert_approx_eq;
629

            
630
    use super::*;
631

            
632
    type TestFoo = BoundedInt32<1, 5>;
633
    type TestBar = BoundedInt32<-45, 17>;
634

            
635
    //make_parameter_type! {TestFoo(3,)}
636
    #[test]
637
    fn entire_range_parsed() {
638
        let x: TestFoo = "1".parse().unwrap();
639
        assert!(x.get() == 1);
640
        let x: TestFoo = "2".parse().unwrap();
641
        assert!(x.get() == 2);
642
        let x: TestFoo = "3".parse().unwrap();
643
        assert!(x.get() == 3);
644
        let x: TestFoo = "4".parse().unwrap();
645
        assert!(x.get() == 4);
646
        let x: TestFoo = "5".parse().unwrap();
647
        assert!(x.get() == 5);
648
    }
649

            
650
    #[test]
651
    fn saturating() {
652
        let x: TestFoo = TestFoo::saturating_new(1000);
653
        let x_val: i32 = x.into();
654
        assert!(x_val == TestFoo::UPPER);
655
        let x: TestFoo = TestFoo::saturating_new(0);
656
        let x_val: i32 = x.into();
657
        assert!(x_val == TestFoo::LOWER);
658
    }
659
    #[test]
660
    fn saturating_string() {
661
        let x: TestFoo = TestFoo::saturating_from_str("1000").unwrap();
662
        let x_val: i32 = x.into();
663
        assert!(x_val == TestFoo::UPPER);
664
        let x: TestFoo = TestFoo::saturating_from_str("0").unwrap();
665
        let x_val: i32 = x.into();
666
        assert!(x_val == TestFoo::LOWER);
667
    }
668

            
669
    #[test]
670
    fn errors_correct() {
671
        let x: Result<TestBar, Error> = "1000".parse();
672
        assert!(x.unwrap_err() == Error::AboveUpperBound(1000, TestBar::UPPER));
673
        let x: Result<TestBar, Error> = "-1000".parse();
674
        assert!(x.unwrap_err() == Error::BelowLowerBound(-1000, TestBar::LOWER));
675
        let x: Result<TestBar, Error> = "xyz".parse();
676
        assert!(x.unwrap_err() == Error::Unrepresentable);
677
    }
678

            
679
    #[test]
680
    fn display() {
681
        let v = BoundedInt32::<99, 1000>::checked_new(345).unwrap();
682
        assert_eq!(v.to_string(), "345".to_string());
683
    }
684

            
685
    #[test]
686
    #[should_panic]
687
    fn checked_too_high() {
688
        let _: TestBar = "1000".parse().unwrap();
689
    }
690

            
691
    #[test]
692
    #[should_panic]
693
    fn checked_too_low() {
694
        let _: TestBar = "-46".parse().unwrap();
695
    }
696

            
697
    #[test]
698
    fn bounded_to_u64() {
699
        let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
700
        let u: u64 = b.try_into().unwrap();
701
        assert_eq!(u, 77);
702

            
703
        let b: BoundedInt32<-100, 100> = BoundedInt32::checked_new(-77).unwrap();
704
        let u: Result<u64, Error> = b.try_into();
705
        assert!(u.is_err());
706
    }
707

            
708
    #[test]
709
    fn bounded_to_f64() {
710
        let x: BoundedInt32<-100, 100> = BoundedInt32::checked_new(77).unwrap();
711
        let f: f64 = x.into();
712
        assert_approx_eq!(f64, f, 77.0);
713
    }
714

            
715
    #[test]
716
    fn bounded_from_i32() {
717
        let x: Result<BoundedInt32<-100, 100>, _> = 50.try_into();
718
        let y: i32 = x.unwrap().into();
719
        assert_eq!(y, 50);
720

            
721
        let x: Result<BoundedInt32<-100, 100>, _> = 1000.try_into();
722
        assert!(x.is_err());
723
    }
724

            
725
    #[test]
726
    fn into_bool() {
727
        let zero: BoundedInt32<0, 1> = BoundedInt32::saturating_from(0);
728
        let one: BoundedInt32<0, 1> = BoundedInt32::saturating_from(1);
729

            
730
        let f: bool = zero.into();
731
        let t: bool = one.into();
732
        assert!(!f);
733
        assert!(t);
734
    }
735

            
736
    #[test]
737
    fn into_u8() {
738
        let zero: BoundedInt32<0, 255> = BoundedInt32::saturating_from(0);
739
        let one: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1);
740
        let ninety: BoundedInt32<0, 255> = BoundedInt32::saturating_from(90);
741
        let max: BoundedInt32<0, 255> = BoundedInt32::saturating_from(1000);
742

            
743
        let a: u8 = zero.into();
744
        let b: u8 = one.into();
745
        let c: u8 = ninety.into();
746
        let d: u8 = max.into();
747

            
748
        assert_eq!(a, 0);
749
        assert_eq!(b, 1);
750
        assert_eq!(c, 90);
751
        assert_eq!(d, 255);
752
    }
753

            
754
    #[test]
755
    fn into_u32() {
756
        let zero: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(0);
757
        let one: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1);
758
        let ninety: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(90);
759
        let max: BoundedInt32<0, 1000> = BoundedInt32::saturating_from(1000);
760

            
761
        assert_eq!(u32::from(zero), 0);
762
        assert_eq!(u32::from(one), 1);
763
        assert_eq!(u32::from(ninety), 90);
764
        assert_eq!(u32::from(max), 1000);
765

            
766
        let zero: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(0);
767
        let one: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1);
768
        let ninety: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(90);
769
        let max: BoundedInt32<1, 1000> = BoundedInt32::saturating_from(1000);
770

            
771
        assert_eq!(u32::from(zero), 1);
772
        assert_eq!(u32::from(one), 1);
773
        assert_eq!(u32::from(ninety), 90);
774
        assert_eq!(u32::from(max), 1000);
775
    }
776

            
777
    #[test]
778
    fn try_into_usize() {
779
        let b0: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(0);
780
        let b100: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(100);
781
        let bn5: BoundedInt32<-10, 300> = BoundedInt32::saturating_from(-5);
782
        assert_eq!(usize::try_from(b0), Ok(0_usize));
783
        assert_eq!(usize::try_from(b100), Ok(100_usize));
784
        assert_eq!(usize::try_from(bn5), Err(Error::Negative));
785
    }
786

            
787
    #[test]
788
    fn percents() {
789
        type Pct = Percentage<u8>;
790
        let p = Pct::new(100);
791
        assert_eq!(p.as_percent(), 100);
792
        assert_approx_eq!(f64, p.as_fraction(), 1.0);
793

            
794
        let p = Pct::new(0);
795
        assert_eq!(p.as_percent(), 0);
796
        assert_approx_eq!(f64, p.as_fraction(), 0.0);
797

            
798
        let p = Pct::new(25);
799
        assert_eq!(p.as_percent(), 25);
800
        assert_eq!(p.clone(), p);
801
        assert_approx_eq!(f64, p.as_fraction(), 0.25);
802

            
803
        type BPct = Percentage<BoundedInt32<0, 100>>;
804
        assert_eq!(BPct::try_from(99).unwrap().as_percent().get(), 99);
805
    }
806

            
807
    #[test]
808
    fn milliseconds() {
809
        type Msec = IntegerMilliseconds<i32>;
810

            
811
        let ms = Msec::new(500);
812
        let d: Result<Duration, _> = ms.try_into();
813
        assert_eq!(d.unwrap(), Duration::from_millis(500));
814
        assert_eq!(Duration::try_from(ms * 2).unwrap(), Duration::from_secs(1));
815

            
816
        let ms = Msec::new(-100);
817
        let d: Result<Duration, _> = ms.try_into();
818
        assert!(d.is_err());
819

            
820
        type BMSec = IntegerMilliseconds<BoundedInt32<0, 1000>>;
821
        let half_sec = BMSec::try_from(500).unwrap();
822
        assert_eq!(
823
            Duration::try_from(half_sec).unwrap(),
824
            Duration::from_millis(500)
825
        );
826
        assert!(BMSec::try_from(1001).is_err());
827
    }
828

            
829
    #[test]
830
    fn seconds() {
831
        type Sec = IntegerSeconds<i32>;
832

            
833
        let ms = Sec::new(500);
834
        let d: Result<Duration, _> = ms.try_into();
835
        assert_eq!(d.unwrap(), Duration::from_secs(500));
836

            
837
        let ms = Sec::new(-100);
838
        let d: Result<Duration, _> = ms.try_into();
839
        assert!(d.is_err());
840

            
841
        type BSec = IntegerSeconds<BoundedInt32<0, 3600>>;
842
        let half_hour = BSec::try_from(1800).unwrap();
843
        assert_eq!(
844
            Duration::try_from(half_hour).unwrap(),
845
            Duration::from_secs(1800)
846
        );
847
        assert!(BSec::try_from(9999).is_err());
848
        assert_eq!(half_hour.clone(), half_hour);
849
    }
850

            
851
    #[test]
852
    fn minutes() {
853
        type Min = IntegerMinutes<i32>;
854

            
855
        let t = Min::new(500);
856
        let d: Duration = t.try_into().unwrap();
857
        assert_eq!(d, Duration::from_secs(500 * 60));
858

            
859
        let t = Min::new(-100);
860
        let d: Result<Duration, _> = t.try_into();
861
        assert_eq!(d, Err(Error::Overflow));
862

            
863
        let t = IntegerMinutes::<u64>::new(u64::MAX);
864
        let d: Result<Duration, _> = t.try_into();
865
        assert_eq!(d, Err(Error::Overflow));
866

            
867
        type BMin = IntegerMinutes<BoundedInt32<10, 30>>;
868
        assert_eq!(
869
            BMin::new(17_i32.try_into().unwrap()),
870
            BMin::try_from(17).unwrap()
871
        );
872
    }
873

            
874
    #[test]
875
    fn days() {
876
        type Days = IntegerDays<i32>;
877

            
878
        let t = Days::new(500);
879
        let d: Duration = t.try_into().unwrap();
880
        assert_eq!(d, Duration::from_secs(500 * 86400));
881

            
882
        let t = Days::new(-100);
883
        let d: Result<Duration, _> = t.try_into();
884
        assert_eq!(d, Err(Error::Overflow));
885

            
886
        let t = IntegerDays::<u64>::new(u64::MAX);
887
        let d: Result<Duration, _> = t.try_into();
888
        assert_eq!(d, Err(Error::Overflow));
889

            
890
        type BDays = IntegerDays<BoundedInt32<10, 30>>;
891
        assert_eq!(
892
            BDays::new(17_i32.try_into().unwrap()),
893
            BDays::try_from(17).unwrap()
894
        );
895
    }
896

            
897
    #[test]
898
    fn sendme() {
899
        let smv = SendMeVersion::new(5);
900
        assert_eq!(smv.get(), 5);
901
        assert_eq!(smv.clone().get(), 5);
902
        assert_eq!(smv, SendMeVersion::try_from(5).unwrap());
903
    }
904
}