advent_of_code_client/
problem.rs

1use std::fmt::Display;
2
3use derive_getters::{Dissolve, Getters};
4
5/// Represents a problem for Advent of Code.
6///
7/// There are a few different ways to create a `Problem`:
8/// with `new`, a `(Year, Day)` tuple implements `From`, or
9/// `(u16, u8)` tuple implements `TryFrom` by validating a
10/// valid year and day is provided.
11///
12/// ```rust
13/// # use advent_of_code_client::{Problem, Year};
14/// let a = Problem::new(Year::Y2017, 4);
15/// let b = (Year::Y2017, 4).into();
16/// let c = (2017_u16, 4_u8).try_into().unwrap();
17///
18/// assert_eq!(a, b);
19/// assert_eq!(b, c);
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Getters, Dissolve)]
22pub struct Problem {
23    year: Year,
24    day: Day,
25}
26
27impl Problem {
28    /// Create a new problem for a given year and day.
29    pub const fn new(year: Year, day: Day) -> Self {
30        Self { year, day }
31    }
32}
33
34impl From<(Year, Day)> for Problem {
35    fn from((year, day): (Year, Day)) -> Self {
36        Self { year, day }
37    }
38}
39
40impl TryFrom<(u16, u8)> for Problem {
41    type Error = String;
42
43    fn try_from((year, day): (u16, u8)) -> Result<Self, Self::Error> {
44        let Some(year) = Year::from_repr(year) else {
45            return Err(format!(
46                "Invalid year provided. Valid years are from 2015 to {}",
47                Year::max()
48            ));
49        };
50        if day == 0 || day > 25 {
51            return Err("Invalid day provided. Valid days are from 1 to 25".to_string());
52        }
53
54        Ok(Self { year, day })
55    }
56}
57
58impl Display for Problem {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(f, "{:?}/{}", self.year, self.day)
61    }
62}
63
64/// Represents a year of advent of code challenges.
65#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum, strum::FromRepr)]
66#[cfg_attr(test, derive(fake::Dummy))]
67#[repr(u16)]
68pub enum Year {
69    Y2016 = 2016,
70    Y2017 = 2017,
71    Y2018 = 2018,
72    Y2019 = 2019,
73    Y2020 = 2020,
74    Y2021 = 2021,
75    Y2022 = 2022,
76    Y2023 = 2023,
77}
78
79impl Year {
80    pub const fn as_int(self) -> u16 {
81        self as u16
82    }
83
84    pub const fn max() -> Year {
85        Self::Y2023
86    }
87}
88
89impl Display for Year {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        write!(f, "{:?}", self)
92    }
93}
94
95/// Indicates the day for a given problem.
96pub type Day = u8;
97
98/// Indicates the level for a given daily problem. Also known as part 1 and 2.
99#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)]
100pub enum Level {
101    A = 1,
102    B = 2,
103}
104
105impl Level {
106    pub fn as_int(self) -> u8 {
107        self as u8
108    }
109}
110
111impl From<u8> for Level {
112    fn from(value: u8) -> Self {
113        match value {
114            1 => Self::A,
115            2 => Self::B,
116            _ => panic!("Cannot convert value {} to Level", value),
117        }
118    }
119}
120
121#[cfg(test)]
122mod test {
123    use std::convert::Into;
124
125    use fake::{Fake, Faker};
126
127    use super::*;
128
129    #[test]
130    fn from_year_and_day_tuple() {
131        let year: Year = Faker.fake();
132        let day: Day = (1..=25).fake();
133        assert_eq!(Into::<Problem>::into((year, day)), Problem { year, day });
134    }
135}