1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use nutype::nutype;
use std::ops::Add;

use crate::Error;

const MM_PER_INCH: f64 = 25.4;

/// Precipitation in mm
#[nutype(
    validate(greater_or_equal = 0.0),
    derive(
        Display,
        TryFrom,
        AsRef,
        Serialize,
        Deserialize,
        Copy,
        Clone,
        PartialEq,
        Debug
    )
)]
pub struct Precipitation(f64);

impl Default for Precipitation {
    fn default() -> Self {
        Self::new(0.0).unwrap()
    }
}

impl Add for Precipitation {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self::new(self.into_inner().add(rhs.into_inner())).unwrap()
    }
}

impl Precipitation {
    /// ```
    /// use weather_util_rust::precipitation::Precipitation;
    /// # use anyhow::Error;
    /// # fn main() -> Result<(), Error> {
    /// let rain = Precipitation::from_inches(1.0)?;
    /// assert_eq!(rain.millimeters(), 25.4);
    /// # Ok(())
    /// # }
    /// ```
    /// # Errors
    ///
    /// Will return error if input is less than zero
    pub fn from_millimeters(precip: f64) -> Result<Self, Error> {
        Self::new(precip).map_err(Into::into)
    }

    /// # Errors
    ///
    /// Will return error if input is less than zero
    pub fn from_inches(precip: f64) -> Result<Self, Error> {
        Self::new(precip * MM_PER_INCH).map_err(Into::into)
    }

    #[inline]
    #[must_use]
    pub fn millimeters(self) -> f64 {
        self.into_inner()
    }

    #[inline]
    #[must_use]
    pub fn inches(self) -> f64 {
        self.into_inner() / MM_PER_INCH
    }
}

#[cfg(test)]
mod test {
    use std::convert::TryFrom;

    use crate::{
        precipitation::{Precipitation, MM_PER_INCH},
        Error,
    };

    #[test]
    fn test_precipitation() -> Result<(), Error> {
        let p = Precipitation::new(1.0)?;
        assert_eq!(p.millimeters(), 1.0);
        assert_eq!(p.inches(), 1.0 / MM_PER_INCH);
        let p2 = Precipitation::from_millimeters(1.0)?;
        assert_eq!(p, p2);
        let p = Precipitation::from_inches(1.0)?;
        assert_eq!(p.inches(), 1.0);

        let h = Precipitation::try_from(-1.0).map_err(Into::<Error>::into);
        assert_eq!(&format!("{h:?}"), "Err(PrecipitationError(GreaterOrEqualViolated))");
        Ok(())
    }
}