use std::fmt;
use std::sync::LazyLock;
use regex::Regex;
use crate::error::{Error, Result};
static SCORE_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(-?\d+)hard/(-?\d+)medium/(-?\d+)soft$").unwrap());
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Score {
pub hard: i64,
pub medium: i64,
pub soft: i64,
}
impl Score {
pub fn parse(value: &str) -> Result<Score> {
let caps = SCORE_PATTERN
.captures(value)
.ok_or_else(|| Error::InvalidScore(value.to_string()))?;
Ok(Score {
hard: caps[1].parse().unwrap(),
medium: caps[2].parse().unwrap(),
soft: caps[3].parse().unwrap(),
})
}
pub fn is_feasible(&self) -> bool {
self.hard >= 0
}
}
impl fmt::Display for Score {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}hard/{}medium/{}soft",
self.hard, self.medium, self.soft
)
}
}
impl std::str::FromStr for Score {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Score::parse(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_valid_score() {
let s = Score::parse("-1hard/0medium/25soft").unwrap();
assert_eq!(
s,
Score {
hard: -1,
medium: 0,
soft: 25
}
);
assert_eq!(s.to_string(), "-1hard/0medium/25soft");
assert!(!s.is_feasible());
}
#[test]
fn rejects_garbage() {
assert!(Score::parse("not-a-score").is_err());
assert!(Score::parse("").is_err());
}
}