Skip to main content

guitar_tab_generator/
string_number.rs

1use crate::error::TabError;
2use std::fmt;
3
4/// A validated guitar string number in the range `1..=12`.
5///
6/// String numbers follow guitar convention: string 1 is the highest-pitched (thinnest)
7/// string, and higher numbers designate lower-pitched strings.
8#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
9pub struct StringNumber(u8);
10impl StringNumber {
11    /// Upper bound enforced by [`StringNumber::new`].
12    pub const MAX: u8 = 12;
13
14    /// Constructs a `StringNumber` after validating that `string_number` is in `1..=MAX`.
15    ///
16    /// # Errors
17    ///
18    /// Returns [`TabError::StringNumberOutOfRange`] if `string_number` is `0` or exceeds
19    /// [`StringNumber::MAX`].
20    pub fn new(string_number: u8) -> Result<Self, TabError> {
21        match string_number {
22            0 => Err(TabError::StringNumberOutOfRange {
23                value: 0,
24                max: Self::MAX,
25            }),
26            1..=Self::MAX => Ok(StringNumber(string_number)),
27            _ => Err(TabError::StringNumberOutOfRange {
28                value: string_number,
29                max: Self::MAX,
30            }),
31        }
32    }
33    /// Returns the underlying `u8`.
34    #[inline]
35    #[must_use]
36    pub fn get(self) -> u8 {
37        self.0
38    }
39}
40#[cfg(test)]
41mod test_create_string_number {
42    use super::*;
43    #[test]
44    fn valid_simple() {
45        assert!(StringNumber::new(1).is_ok());
46    }
47
48    #[test]
49    fn invalid() {
50        for n in [0u8, 13, 100, 255] {
51            assert!(StringNumber::new(n).is_err(), "n={n} must be Err");
52        }
53    }
54
55    #[test]
56    fn returns_typed_error_for_zero() {
57        let err = StringNumber::new(0).unwrap_err();
58        match err {
59            crate::error::TabError::StringNumberOutOfRange { value, max } => {
60                assert_eq!(value, 0);
61                assert_eq!(max, 12);
62            }
63            other => panic!("expected StringNumberOutOfRange, got {other:?}"),
64        }
65    }
66
67    #[test]
68    fn returns_typed_error_for_above_max() {
69        let err = StringNumber::new(13).unwrap_err();
70        match err {
71            crate::error::TabError::StringNumberOutOfRange { value, max } => {
72                assert_eq!(value, 13);
73                assert_eq!(max, 12);
74            }
75            other => panic!("expected StringNumberOutOfRange, got {other:?}"),
76        }
77    }
78}
79
80impl fmt::Debug for StringNumber {
81    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        match self.0 {
83            1 => f.write_str("1_e"),
84            2 => f.write_str("2_B"),
85            3 => f.write_str("3_G"),
86            4 => f.write_str("4_D"),
87            5 => f.write_str("5_A"),
88            6 => f.write_str("6_E"),
89            n => write!(f, "{n}"),
90        }
91    }
92}
93#[cfg(test)]
94mod test_pitch_debug {
95    use super::*;
96    #[test]
97    fn strings_1_to_6() {
98        assert_eq!(format!("{:?}", StringNumber::new(1).unwrap()), "1_e");
99        assert_eq!(format!("{:?}", StringNumber::new(2).unwrap()), "2_B");
100        assert_eq!(format!("{:?}", StringNumber::new(3).unwrap()), "3_G");
101        assert_eq!(format!("{:?}", StringNumber::new(4).unwrap()), "4_D");
102        assert_eq!(format!("{:?}", StringNumber::new(5).unwrap()), "5_A");
103        assert_eq!(format!("{:?}", StringNumber::new(6).unwrap()), "6_E");
104    }
105    #[test]
106    fn string_greater_than_6() {
107        assert_eq!(format!("{:?}", StringNumber::new(8).unwrap()), "8");
108    }
109}