rating/
rating.rs

1use rust_decimal::prelude::ToPrimitive;
2use rust_decimal::{Decimal, RoundingStrategy};
3
4#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
5pub struct Rating(Decimal);
6
7impl Rating {
8    /// Rating rounded to the units
9    ///
10    /// Useful when we don't want to display `Rating` with decimal places
11    pub fn round_to_i32(&self) -> i32 {
12        self.round_dp_with_strategy(0, RoundingStrategy::MidpointAwayFromZero)
13            .to_i32()
14            .unwrap_or(0)
15    }
16
17    pub fn rounded(&self, decimal_places: u32) -> f64 {
18        self.round_dp_with_strategy(decimal_places, RoundingStrategy::MidpointAwayFromZero)
19            .to_f64()
20            .unwrap_or(0.0)
21    }
22
23    /// Rating rounded to the first decimal digit
24    ///
25    /// Typically, most rating systems round to one decimal digit
26    pub fn value(&self) -> f64 {
27        self.rounded(1)
28    }
29}
30
31impl From<Decimal> for Rating {
32    fn from(value: Decimal) -> Self {
33        Rating(value)
34    }
35}
36
37impl From<i32> for Rating {
38    fn from(value: i32) -> Self {
39        Rating::from(Decimal::from(value))
40    }
41}
42
43impl From<i64> for Rating {
44    fn from(value: i64) -> Self {
45        Rating::from(Decimal::from(value))
46    }
47}
48
49impl std::ops::Deref for Rating {
50    type Target = Decimal;
51
52    fn deref(&self) -> &Self::Target {
53        &self.0
54    }
55}
56
57impl std::ops::Add<Decimal> for Rating {
58    type Output = Rating;
59
60    fn add(self, rhs: Decimal) -> Self::Output {
61        Rating::from(*self + rhs)
62    }
63}
64
65impl std::ops::Sub<Decimal> for Rating {
66    type Output = Rating;
67
68    fn sub(self, rhs: Decimal) -> Self::Output {
69        Rating::from(*self - rhs)
70    }
71}
72
73impl std::fmt::Display for Rating {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}", self.value())
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use rust_decimal::prelude::FromPrimitive;
83    use test_case::test_case;
84
85    #[test_case(1.23 => 1.2)]
86    #[test_case(1.25 => 1.3)]
87    #[test_case(1.0005 => 1.0)]
88    #[test_case(1.89 => 1.9)]
89    #[test_case(1.849 => 1.8)]
90    fn rounds_to_1_decimal_place_by_default(value: f64) -> f64 {
91        Rating::from(Decimal::from_f64(value).unwrap()).value()
92    }
93}