use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Letter {
F,
E,
D,
C,
B,
A,
#[serde(rename = "A+")]
APlus,
}
impl Letter {
pub fn from_score(score: u32) -> Self {
match score {
95..=u32::MAX => Letter::APlus,
85..=94 => Letter::A,
75..=84 => Letter::B,
65..=74 => Letter::C,
50..=64 => Letter::D,
30..=49 => Letter::E,
_ => Letter::F,
}
}
pub fn as_str(self) -> &'static str {
match self {
Letter::APlus => "A+",
Letter::A => "A",
Letter::B => "B",
Letter::C => "C",
Letter::D => "D",
Letter::E => "E",
Letter::F => "F",
}
}
}
impl fmt::Display for Letter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Letter {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_uppercase().as_str() {
"A+" => Ok(Letter::APlus),
"A" => Ok(Letter::A),
"B" => Ok(Letter::B),
"C" => Ok(Letter::C),
"D" => Ok(Letter::D),
"E" => Ok(Letter::E),
"F" => Ok(Letter::F),
other => Err(format!(
"invalid grade '{other}' (expected A+, A, B, C, D, E or F)"
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn score_bands_match_the_header_audit_scale() {
for (score, letter) in [
(100, Letter::APlus),
(95, Letter::APlus),
(94, Letter::A),
(85, Letter::A),
(84, Letter::B),
(75, Letter::B),
(74, Letter::C),
(65, Letter::C),
(64, Letter::D),
(50, Letter::D),
(49, Letter::E),
(30, Letter::E),
(29, Letter::F),
(0, Letter::F),
] {
assert_eq!(Letter::from_score(score), letter, "score {score}");
}
}
#[test]
fn letters_order_worst_to_best() {
assert!(Letter::F < Letter::E);
assert!(Letter::C < Letter::B);
assert!(Letter::A < Letter::APlus);
assert_eq!(Letter::APlus.min(Letter::C), Letter::C);
}
#[test]
fn parses_and_prints_round_trip() {
for letter in [
Letter::F,
Letter::E,
Letter::D,
Letter::C,
Letter::B,
Letter::A,
Letter::APlus,
] {
assert_eq!(letter.as_str().parse::<Letter>(), Ok(letter));
assert_eq!(letter.to_string(), letter.as_str());
}
assert_eq!(" a+ ".parse::<Letter>(), Ok(Letter::APlus));
assert!("Z".parse::<Letter>().is_err());
}
#[test]
fn serializes_as_the_printed_letter() {
assert_eq!(serde_json::to_string(&Letter::APlus).unwrap(), "\"A+\"");
assert_eq!(serde_json::to_string(&Letter::C).unwrap(), "\"C\"");
}
}