use std::{fmt, str::FromStr}; use evtclib::{EliteSpec, Profession}; use thiserror::Error; /// An enum containing either a profession or an elite spec. /// /// This enum provides us with a variety of things: /// /// Game mechanic wise, a Dragonhunter is also a Guardian, because Dragonhunter is only the elite /// specialization. However, when outputting that to the user, we usually only write Dragonhunter, /// and not Guardian, as that is implied. Same when filtering, when we filter for Guardian, we /// probably don't want any Dragonhunters. /// /// So this enum unifies the handling between core specs and elite specs, and provides them with a /// convenient [`Display`][fmt::Display] implementation as well. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum PlayerClass { Profession(Profession), EliteSpec(EliteSpec), } #[derive(Debug, Clone, Hash, PartialEq, Eq, Error)] #[error("could not parse the class: {0}")] pub struct ParsePlayerClassError(String); impl From for PlayerClass { fn from(p: Profession) -> Self { PlayerClass::Profession(p) } } impl From for PlayerClass { fn from(e: EliteSpec) -> Self { PlayerClass::EliteSpec(e) } } impl FromStr for PlayerClass { type Err = ParsePlayerClassError; fn from_str(s: &str) -> Result { let err = ParsePlayerClassError(s.to_owned()); Profession::from_str(s) .map(Into::into) .map_err(|_| err.clone()) .or_else(|_| EliteSpec::from_str(s).map(Into::into).map_err(|_| err)) } } impl From<(Profession, Option)> for PlayerClass { fn from((profession, elite_spec): (Profession, Option)) -> Self { if let Some(spec) = elite_spec { spec.into() } else { profession.into() } } } impl From<&evtclib::Player> for PlayerClass { fn from(player: &evtclib::Player) -> Self { (player.profession(), player.elite()).into() } } impl fmt::Display for PlayerClass { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { PlayerClass::EliteSpec(elite) => elite.fmt(f), PlayerClass::Profession(prof) => prof.fmt(f), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_player_class_from() { let tests: &[(Profession, Option, PlayerClass)] = &[ ( Profession::Guardian, None, PlayerClass::Profession(Profession::Guardian), ), ( Profession::Guardian, Some(EliteSpec::Dragonhunter), PlayerClass::EliteSpec(EliteSpec::Dragonhunter), ), ]; for (prof, elite_spec, expected) in tests { assert_eq!(PlayerClass::from((*prof, *elite_spec)), *expected); } } #[test] fn test_parse_player_class() { let tests: &[(&'static str, PlayerClass)] = &[ ("guardian", Profession::Guardian.into()), ("dragonhunter", EliteSpec::Dragonhunter.into()), ("warrior", Profession::Warrior.into()), ("scourge", EliteSpec::Scourge.into()), ]; for (input, expected) in tests { assert_eq!(input.parse(), Ok(*expected)); } } }