nhl_api/
ids.rs

1use std::fmt;
2use std::str::FromStr;
3
4/// A unique NHL game identifier
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
6pub struct GameId(i64);
7
8impl GameId {
9    /// Create a new GameId from an integer
10    pub const fn new(id: i64) -> Self {
11        Self(id)
12    }
13
14    /// Get the inner value
15    pub const fn as_i64(&self) -> i64 {
16        self.0
17    }
18}
19
20impl From<i64> for GameId {
21    fn from(id: i64) -> Self {
22        Self(id)
23    }
24}
25
26impl From<GameId> for i64 {
27    fn from(id: GameId) -> i64 {
28        id.0
29    }
30}
31
32impl fmt::Display for GameId {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(f, "{}", self.0)
35    }
36}
37
38impl FromStr for GameId {
39    type Err = std::num::ParseIntError;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        Ok(Self(s.parse()?))
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use std::collections::{HashMap, HashSet};
50
51    #[test]
52    fn test_game_id_new() {
53        let id = GameId::new(2023020001);
54        assert_eq!(id.as_i64(), 2023020001);
55    }
56
57    #[test]
58    fn test_game_id_new_negative() {
59        let id = GameId::new(-1);
60        assert_eq!(id.as_i64(), -1);
61    }
62
63    #[test]
64    fn test_game_id_new_zero() {
65        let id = GameId::new(0);
66        assert_eq!(id.as_i64(), 0);
67    }
68
69    #[test]
70    fn test_game_id_as_i64() {
71        let id = GameId::new(12345);
72        assert_eq!(id.as_i64(), 12345);
73    }
74
75    #[test]
76    fn test_game_id_to_string() {
77        let id = GameId::new(2023020001);
78        assert_eq!(id.to_string(), "2023020001");
79
80        let negative_id = GameId::new(-42);
81        assert_eq!(negative_id.to_string(), "-42");
82    }
83
84    #[test]
85    fn test_game_id_from_i64() {
86        let id: GameId = 2023020001_i64.into();
87        assert_eq!(id.as_i64(), 2023020001);
88
89        let id2 = GameId::from(9876543210_i64);
90        assert_eq!(id2.as_i64(), 9876543210);
91    }
92
93    #[test]
94    fn test_i64_from_game_id() {
95        let id = GameId::new(2023020001);
96        let value: i64 = id.into();
97        assert_eq!(value, 2023020001);
98    }
99
100    #[test]
101    fn test_game_id_display() {
102        let id = GameId::new(2024030405);
103        assert_eq!(format!("{}", id), "2024030405");
104
105        let negative_id = GameId::new(-123);
106        assert_eq!(format!("{}", negative_id), "-123");
107    }
108
109    #[test]
110    fn test_game_id_from_str() {
111        let id = GameId::from_str("2023020001").unwrap();
112        assert_eq!(id.as_i64(), 2023020001);
113
114        let id2: GameId = "9876543210".parse().unwrap();
115        assert_eq!(id2.as_i64(), 9876543210);
116
117        let negative_id: GameId = "-42".parse().unwrap();
118        assert_eq!(negative_id.as_i64(), -42);
119    }
120
121    #[test]
122    fn test_game_id_from_str_invalid() {
123        // Non-numeric string
124        assert!(GameId::from_str("not-a-number").is_err());
125        assert!(GameId::from_str("abc123").is_err());
126        assert!(GameId::from_str("").is_err());
127
128        // Invalid formats
129        assert!(GameId::from_str("12.34").is_err());
130        assert!(GameId::from_str("12 34").is_err());
131
132        // Overflow
133        assert!(GameId::from_str("999999999999999999999999999").is_err());
134    }
135
136    #[test]
137    fn test_game_id_equality() {
138        let id1 = GameId::new(2023020001);
139        let id2 = GameId::new(2023020001);
140        let id3 = GameId::new(2023020002);
141
142        assert_eq!(id1, id2);
143        assert_ne!(id1, id3);
144        assert_ne!(id2, id3);
145    }
146
147    #[test]
148    fn test_game_id_ordering() {
149        let id1 = GameId::new(100);
150        let id2 = GameId::new(200);
151        let id3 = GameId::new(200);
152
153        assert!(id1 < id2);
154        assert!(id2 > id1);
155        assert!(id2 <= id3);
156        assert!(id2 >= id3);
157        assert_eq!(id2.cmp(&id3), std::cmp::Ordering::Equal);
158        assert_eq!(id1.cmp(&id2), std::cmp::Ordering::Less);
159        assert_eq!(id2.cmp(&id1), std::cmp::Ordering::Greater);
160    }
161
162    #[test]
163    fn test_game_id_ordering_negative() {
164        let id1 = GameId::new(-100);
165        let id2 = GameId::new(0);
166        let id3 = GameId::new(100);
167
168        assert!(id1 < id2);
169        assert!(id2 < id3);
170        assert!(id1 < id3);
171    }
172
173    #[test]
174    fn test_game_id_hash() {
175        let mut set = HashSet::new();
176        let id1 = GameId::new(2023020001);
177        let id2 = GameId::new(2023020001);
178        let id3 = GameId::new(2023020002);
179
180        set.insert(id1);
181        assert!(set.contains(&id2)); // Should find id2 since it equals id1
182        assert!(!set.contains(&id3));
183
184        set.insert(id3);
185        assert_eq!(set.len(), 2);
186    }
187
188    #[test]
189    fn test_game_id_as_map_key() {
190        let mut map = HashMap::new();
191        let id1 = GameId::new(2023020001);
192        let id2 = GameId::new(2023020002);
193
194        map.insert(id1, "Game 1");
195        map.insert(id2, "Game 2");
196
197        assert_eq!(map.get(&id1), Some(&"Game 1"));
198        assert_eq!(map.get(&id2), Some(&"Game 2"));
199        assert_eq!(map.get(&GameId::new(2023020001)), Some(&"Game 1"));
200    }
201
202    #[test]
203    fn test_game_id_const() {
204        const GAME_ID: GameId = GameId::new(2023020001);
205        assert_eq!(GAME_ID.as_i64(), 2023020001);
206    }
207
208    #[test]
209    fn test_game_id_copy_clone() {
210        let id1 = GameId::new(2023020001);
211        let id2 = id1; // Copy
212        let id3 = id1; // GameId is Copy, so no need for .clone()
213
214        assert_eq!(id1, id2);
215        assert_eq!(id1, id3);
216        assert_eq!(id2, id3);
217    }
218}