Skip to main content

pickems/datatypes/
rating.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3/// Representation of team rating.
4#[derive(Debug, Clone, Copy)]
5pub struct Rating(u16);
6
7impl Rating {
8    pub fn try_new(rating: u16) -> anyhow::Result<Self> {
9        if rating == 0 {
10            anyhow::bail!("invalid rating: must be greater than 0");
11        }
12
13        Ok(Self(rating))
14    }
15
16    #[must_use]
17    pub fn new(rating: u16) -> Self {
18        Self::try_new(rating).unwrap()
19    }
20
21    /// # Safety
22    /// Must ensure that `rating` is greater than zero.
23    #[must_use]
24    pub const unsafe fn new_unchecked(rating: u16) -> Self {
25        Self(rating)
26    }
27
28    /// Return the rating as `f32` for probability calculations.
29    #[must_use]
30    pub fn to_f32(self) -> f32 {
31        f32::from(self.0)
32    }
33}
34
35impl std::fmt::Display for Rating {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        self.0.fmt(f)
38    }
39}
40
41impl Serialize for Rating {
42    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
43        serializer.serialize_u16(self.0)
44    }
45}
46
47impl<'de> Deserialize<'de> for Rating {
48    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
49        Self::try_new(u16::deserialize(deserializer)?).map_err(serde::de::Error::custom)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[derive(Deserialize)]
58    struct Input {
59        rating: Rating,
60    }
61
62    #[test]
63    fn validates_ratings() {
64        assert!(Rating::try_new(1).is_ok());
65        assert!(Rating::try_new(0).is_err());
66    }
67
68    #[test]
69    fn rejects_invalid_deserialized_ratings() {
70        let input: Input = toml::from_str("rating = 2000").unwrap();
71        assert!((input.rating.to_f32() - 2000.0).abs() < f32::EPSILON);
72        assert!(toml::from_str::<Input>("rating = 0").is_err());
73    }
74}