rosu_v2/model/
grade.rs

1use crate::error::{OsuError, ParsingError};
2
3use serde::{
4    de::{Error, Unexpected, Visitor},
5    Deserialize, Deserializer,
6};
7use std::{fmt, str::FromStr};
8
9/// Enum for a [`Score`](crate::model::score::Score)'s grade (sometimes called rank)
10#[allow(clippy::upper_case_acronyms, missing_docs)]
11#[derive(Copy, Clone, Hash, Debug, Eq, PartialEq, PartialOrd)]
12#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
13pub enum Grade {
14    F,
15    D,
16    C,
17    B,
18    A,
19    S,
20    SH,
21    X,
22    XH,
23}
24
25impl Grade {
26    /// Check two grades for equality, ignoring silver-/regular-S difference
27    ///
28    /// # Example
29    /// ```
30    /// use rosu_v2::model::Grade;
31    ///
32    /// assert!(Grade::S.eq_letter(Grade::SH));
33    /// assert!(!Grade::X.eq_letter(Grade::SH));
34    /// ```
35    #[inline]
36    pub fn eq_letter(self, other: Grade) -> bool {
37        match self {
38            Grade::XH | Grade::X => other == Grade::XH || other == Grade::X,
39            Grade::SH | Grade::S => other == Grade::SH || other == Grade::S,
40            _ => self == other,
41        }
42    }
43}
44
45impl FromStr for Grade {
46    type Err = OsuError;
47
48    fn from_str(grade: &str) -> Result<Self, Self::Err> {
49        let grade = match grade.to_uppercase().as_str() {
50            "XH" | "SSH" => Self::XH,
51            "X" | "SS" => Self::X,
52            "SH" => Self::SH,
53            "S" => Self::S,
54            "A" => Self::A,
55            "B" => Self::B,
56            "C" => Self::C,
57            "D" => Self::D,
58            "F" => Self::F,
59            _ => return Err(ParsingError::Grade(grade.to_owned()).into()),
60        };
61
62        Ok(grade)
63    }
64}
65
66impl fmt::Display for Grade {
67    #[inline]
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        fmt::Debug::fmt(self, f)
70    }
71}
72
73struct GradeVisitor;
74
75impl Visitor<'_> for GradeVisitor {
76    type Value = Grade;
77
78    #[inline]
79    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
80        formatter.write_str("a string")
81    }
82
83    #[inline]
84    fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
85        Grade::from_str(v).map_err(|_| Error::invalid_value(Unexpected::Str(v), &"a grade string"))
86    }
87}
88
89impl<'de> Deserialize<'de> for Grade {
90    #[inline]
91    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
92        d.deserialize_any(GradeVisitor)
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn grade_eq() {
102        assert!(Grade::SH.eq_letter(Grade::S));
103    }
104
105    #[test]
106    fn grade_neq() {
107        assert!(!Grade::S.eq_letter(Grade::A));
108    }
109
110    #[test]
111    fn grade_ord() {
112        assert!(Grade::S > Grade::A);
113    }
114}