1use crate::error::{OsuError, ParsingError};
2
3use serde::{
4 de::{Error, Unexpected, Visitor},
5 Deserialize, Deserializer,
6};
7use std::{fmt, str::FromStr};
8
9#[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 #[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}