1const MAX_BOXES_COUNT: u16 = 4096;
2const MAX_PUSHERS_COUNT: u16 = 512;
3
4use std::fmt::{Display, Formatter};
5
6use super::errors::{PieceIdError, PieceIdErrorKind};
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
46#[cfg_attr(
47 feature = "serde",
48 derive(serde::Serialize, serde::Deserialize),
49 serde(try_from = "u16", into = "u16")
50)]
51pub struct BoxID(u16);
52
53#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59#[cfg_attr(
60 feature = "serde",
61 derive(serde::Serialize, serde::Deserialize),
62 serde(try_from = "u16", into = "u16")
63)]
64pub struct GoalID(u16);
65
66#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
72#[cfg_attr(
73 feature = "serde",
74 derive(serde::Serialize, serde::Deserialize),
75 serde(try_from = "u16", into = "u16")
76)]
77pub struct PusherID(u16);
78
79macro_rules! implement_piece_id_construct_from_int {
80 ( $PieceIdTy:ident, $ErrKind:expr, u8 ) => {
81 impl From<u8> for $PieceIdTy {
82 fn from(id: u8) -> Self { Self(id.into()) }
83 }
84 };
85
86 ( $PieceIdTy:ident, $ErrKind:expr, u16 ) => {
87 impl TryFrom<u16> for $PieceIdTy {
88 type Error = PieceIdError;
89 fn try_from(value: u16) -> Result<Self, Self::Error> {
90 if value > Self::MAX.into() {
91 Err(PieceIdError {
92 kind: $ErrKind,
93 value: value.to_string(),
94 })
95 } else {
96 Ok(Self(value))
97 }
98 }
99 }
100 };
101
102 ( $PieceIdTy:ident, $ErrKind:expr, $IntTy:ty ) => {
103 impl TryFrom<$IntTy> for $PieceIdTy {
104 type Error = PieceIdError;
105 fn try_from(value: $IntTy) -> Result<Self, Self::Error> {
106 u16::try_from(value)
107 .map_err(|_| {
108 PieceIdError {
109 kind: $ErrKind,
110 value: value.to_string(),
111 }
112 })?
113 .try_into()
114 }
115 }
116 };
117}
118
119macro_rules! implement_piece_id {
120 ( $PieceIdTy:ident, $MaxConst:ident, $ErrKind:expr ) => {
121 impl $PieceIdTy {
122 const FIRST: u16 = 0;
123 pub const MAX: u16 = $MaxConst - 1;
124
125 pub fn new(id: u16) -> Result<Self, PieceIdError> { id.try_into() }
126 }
127
128 impl Display for $PieceIdTy {
129 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }
130 }
131
132 impl Default for $PieceIdTy {
133 fn default() -> Self { Self($PieceIdTy::FIRST) }
134 }
135
136 impl From<$PieceIdTy> for u16 {
139 fn from(value: $PieceIdTy) -> Self { return value.0; }
140 }
141
142 impl From<$PieceIdTy> for usize {
143 fn from(value: $PieceIdTy) -> Self { return value.0.into(); }
144 }
145
146 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u8);
147 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u16);
148 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u32);
149 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, u64);
150 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, usize);
151
152 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i8);
153 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i16);
154 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i32);
155 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, i64);
156 implement_piece_id_construct_from_int!($PieceIdTy, $ErrKind, isize);
157
158 impl PartialEq<u8> for $PieceIdTy {
165 fn eq(&self, other: &u8) -> bool { self.0 == u16::from(*other) }
166 }
167
168 impl PartialEq<u16> for $PieceIdTy {
169 fn eq(&self, other: &u16) -> bool { self.0 == *other }
170 }
171
172 impl PartialEq<u32> for $PieceIdTy {
173 fn eq(&self, other: &u32) -> bool { u32::from(self.0) == *other }
174 }
175
176 impl PartialEq<u64> for $PieceIdTy {
177 fn eq(&self, other: &u64) -> bool { u64::from(self.0) == *other }
178 }
179
180 impl PartialEq<usize> for $PieceIdTy {
181 fn eq(&self, other: &usize) -> bool { usize::from(self.0) == *other }
182 }
183
184 impl PartialEq<i8> for $PieceIdTy {
185 fn eq(&self, other: &i8) -> bool { i32::from(self.0) == i32::from(*other) }
186 }
187
188 impl PartialEq<i16> for $PieceIdTy {
189 fn eq(&self, other: &i16) -> bool { i32::from(self.0) == i32::from(*other) }
190 }
191
192 impl PartialEq<i32> for $PieceIdTy {
193 fn eq(&self, other: &i32) -> bool { i32::from(self.0) == *other }
194 }
195
196 impl PartialEq<i64> for $PieceIdTy {
197 fn eq(&self, other: &i64) -> bool { i64::from(self.0) == *other }
198 }
199
200 impl PartialEq<isize> for $PieceIdTy {
201 fn eq(&self, other: &isize) -> bool {
202 match isize::try_from(self.0) {
203 Ok(self_isize) => self_isize == *other,
204 Err(_) => false,
205 }
206 }
207 }
208 };
209}
210
211implement_piece_id!(BoxID, MAX_BOXES_COUNT, PieceIdErrorKind::InvalidBoxID);
212implement_piece_id!(GoalID, MAX_BOXES_COUNT, PieceIdErrorKind::InvalidGoalID);
213implement_piece_id!(PusherID, MAX_PUSHERS_COUNT, PieceIdErrorKind::InvalidPusherID);
214
215impl PartialEq<GoalID> for BoxID {
216 fn eq(&self, other: &GoalID) -> bool { self.0 == other.0 }
217}
218
219impl PartialEq<BoxID> for GoalID {
220 fn eq(&self, other: &BoxID) -> bool { self.0 == other.0 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_box_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
229 assert!(BoxID::new(42)? == 42_u8);
230
231 assert!(BoxID::from(42_u8) == 42_u8);
232 assert!(BoxID::try_from(42_u16)? == 42_u8);
233 assert!(BoxID::try_from(42_u32)? == 42_u8);
234 assert!(BoxID::try_from(42_u64)? == 42_u8);
235 assert!(BoxID::try_from(42_usize)? == 42_u8);
236
237 assert!(BoxID::try_from(42_i8)? == 42_u8);
238 assert!(BoxID::try_from(42_i16)? == 42_u8);
239 assert!(BoxID::try_from(42_i32)? == 42_u8);
240 assert!(BoxID::try_from(42_i64)? == 42_u8);
241 assert!(BoxID::try_from(42_isize)? == 42_u8);
242
243 assert!(BoxID::try_from(-1_i32).is_err());
244
245 Ok(())
246 }
247
248 #[test]
249 fn test_box_id_is_value_constrained() {
250 assert!(BoxID::try_from(BoxID::FIRST).is_ok());
251 assert!(BoxID::try_from(BoxID::MAX).is_ok());
252 assert!(BoxID::default() == BoxID::FIRST);
253 assert!(BoxID::try_from(usize::from(BoxID::MAX) + 1).is_err());
254 assert!(BoxID::try_from(MAX_BOXES_COUNT - 1).is_ok());
255 assert!(BoxID::try_from(MAX_BOXES_COUNT).is_err());
256 }
257
258 #[test]
259 fn test_box_id_is_comparable() {
260 assert!(BoxID::from(0) == BoxID::from(0));
261 assert!(BoxID::from(0) == 0_u8);
262 assert!(BoxID::from(0) == 0_u16);
263 assert!(BoxID::from(0) == 0_u32);
264 assert!(BoxID::from(0) == 0_u64);
265 assert!(BoxID::from(0) == 0_usize);
266 assert!(BoxID::from(0) == 0_i8);
267 assert!(BoxID::from(0) == 0_i16);
268 assert!(BoxID::from(0) == 0_i32);
269 assert!(BoxID::from(0) == 0_i64);
270 assert!(BoxID::from(0) == 0_isize);
271
272 assert!(BoxID::from(0) != BoxID::from(1));
273 assert!(BoxID::from(0) != 1_u8);
274 assert!(BoxID::from(0) != 1_u16);
275 assert!(BoxID::from(0) != 1_u32);
276 assert!(BoxID::from(0) != 1_u64);
277 assert!(BoxID::from(0) != 1_usize);
278 assert!(BoxID::from(0) != 1_i8);
279 assert!(BoxID::from(0) != 1_i16);
280 assert!(BoxID::from(0) != 1_i32);
281 assert!(BoxID::from(0) != 1_i64);
282 assert!(BoxID::from(0) != 1_isize);
283
284 assert!(BoxID::from(0) == GoalID::from(0));
285 assert!(BoxID::from(0) != GoalID::from(1));
286 }
287
288 #[test]
289 fn test_goal_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
290 assert!(GoalID::new(42)? == 42_u8);
291
292 assert!(GoalID::from(42_u8) == 42_u8);
293 assert!(GoalID::try_from(42_u16)? == 42_u8);
294 assert!(GoalID::try_from(42_u32)? == 42_u8);
295 assert!(GoalID::try_from(42_u64)? == 42_u8);
296 assert!(GoalID::try_from(42_usize)? == 42_u8);
297
298 assert!(GoalID::try_from(42_i8)? == 42_u8);
299 assert!(GoalID::try_from(42_i16)? == 42_u8);
300 assert!(GoalID::try_from(42_i32)? == 42_u8);
301 assert!(GoalID::try_from(42_i64)? == 42_u8);
302 assert!(GoalID::try_from(42_isize)? == 42_u8);
303
304 assert!(GoalID::try_from(-1_i32).is_err());
305
306 Ok(())
307 }
308
309 #[test]
310 fn test_goal_id_is_value_constrained() {
311 assert!(GoalID::try_from(GoalID::FIRST).is_ok());
312 assert!(GoalID::try_from(GoalID::MAX).is_ok());
313 assert!(GoalID::default() == GoalID::FIRST);
314 assert!(GoalID::try_from(usize::from(GoalID::MAX) + 1).is_err());
315 assert!(GoalID::try_from(MAX_BOXES_COUNT - 1).is_ok());
316 assert!(GoalID::try_from(MAX_BOXES_COUNT).is_err());
317 }
318
319 #[test]
320 fn test_goal_id_is_comparable() {
321 assert!(GoalID::from(0) == GoalID::from(0));
322 assert!(GoalID::from(0) == 0_u8);
323 assert!(GoalID::from(0) == 0_u16);
324 assert!(GoalID::from(0) == 0_u32);
325 assert!(GoalID::from(0) == 0_u64);
326 assert!(GoalID::from(0) == 0_usize);
327 assert!(GoalID::from(0) == 0_i8);
328 assert!(GoalID::from(0) == 0_i16);
329 assert!(GoalID::from(0) == 0_i32);
330 assert!(GoalID::from(0) == 0_i64);
331 assert!(GoalID::from(0) == 0_isize);
332
333 assert!(GoalID::from(0) != GoalID::from(1));
334 assert!(GoalID::from(0) != 1_u8);
335 assert!(GoalID::from(0) != 1_u16);
336 assert!(GoalID::from(0) != 1_u32);
337 assert!(GoalID::from(0) != 1_u64);
338 assert!(GoalID::from(0) != 1_usize);
339 assert!(GoalID::from(0) != 1_i8);
340 assert!(GoalID::from(0) != 1_i16);
341 assert!(GoalID::from(0) != 1_i32);
342 assert!(GoalID::from(0) != 1_i64);
343 assert!(GoalID::from(0) != 1_isize);
344
345 assert!(GoalID::from(0) == BoxID::from(0));
346 assert!(GoalID::from(0) != BoxID::from(1));
347 }
348
349 #[test]
350 fn test_pusher_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
351 assert!(PusherID::new(42)? == 42_u8);
352
353 assert!(PusherID::from(42_u8) == 42_u8);
354 assert!(PusherID::try_from(42_u16)? == 42_u8);
355 assert!(PusherID::try_from(42_u32)? == 42_u8);
356 assert!(PusherID::try_from(42_u64)? == 42_u8);
357 assert!(PusherID::try_from(42_usize)? == 42_u8);
358
359 assert!(PusherID::try_from(42_i8)? == 42_u8);
360 assert!(PusherID::try_from(42_i16)? == 42_u8);
361 assert!(PusherID::try_from(42_i32)? == 42_u8);
362 assert!(PusherID::try_from(42_i64)? == 42_u8);
363 assert!(PusherID::try_from(42_isize)? == 42_u8);
364
365 assert!(PusherID::try_from(-1_i32).is_err());
366
367 Ok(())
368 }
369
370 #[test]
371 fn test_pusher_id_is_value_constrained() {
372 assert!(PusherID::try_from(PusherID::FIRST).is_ok());
373 assert!(PusherID::try_from(PusherID::MAX).is_ok());
374 assert!(PusherID::default() == PusherID::FIRST);
375 assert!(PusherID::try_from(usize::from(PusherID::MAX) + 1).is_err());
376 assert!(PusherID::try_from(MAX_PUSHERS_COUNT - 1).is_ok());
377 assert!(PusherID::try_from(MAX_PUSHERS_COUNT).is_err());
378 }
379
380 #[test]
381 fn test_pusher_id_is_comparable() {
382 assert!(PusherID::from(0) == PusherID::from(0));
383 assert!(PusherID::from(0) == 0_u8);
384 assert!(PusherID::from(0) == 0_u16);
385 assert!(PusherID::from(0) == 0_u32);
386 assert!(PusherID::from(0) == 0_u64);
387 assert!(PusherID::from(0) == 0_usize);
388 assert!(PusherID::from(0) == 0_i8);
389 assert!(PusherID::from(0) == 0_i16);
390 assert!(PusherID::from(0) == 0_i32);
391 assert!(PusherID::from(0) == 0_i64);
392 assert!(PusherID::from(0) == 0_isize);
393
394 assert!(PusherID::from(0) != PusherID::from(1));
395 assert!(PusherID::from(0) != 1_u8);
396 assert!(PusherID::from(0) != 1_u16);
397 assert!(PusherID::from(0) != 1_u32);
398 assert!(PusherID::from(0) != 1_u64);
399 assert!(PusherID::from(0) != 1_usize);
400 assert!(PusherID::from(0) != 1_i8);
401 assert!(PusherID::from(0) != 1_i16);
402 assert!(PusherID::from(0) != 1_i32);
403 assert!(PusherID::from(0) != 1_i64);
404 assert!(PusherID::from(0) != 1_isize);
405 }
406
407 #[cfg(feature = "serde")]
408 mod test_piece_id_serde {
409 use std::error::Error;
410
411 use super::*;
412
413 #[test]
414 fn it_serializes_as_a_bare_integer() -> Result<(), Box<dyn Error>> {
415 assert_eq!(serde_json::to_string(&BoxID::from(42_u8))?, "42");
416 assert_eq!(serde_json::to_string(&GoalID::from(42_u8))?, "42");
417 assert_eq!(serde_json::to_string(&PusherID::from(42_u8))?, "42");
418
419 assert_eq!(serde_json::from_str::<BoxID>("42")?, BoxID::from(42_u8));
420 assert_eq!(serde_json::from_str::<GoalID>("42")?, GoalID::from(42_u8));
421 assert_eq!(serde_json::from_str::<PusherID>("42")?, PusherID::from(42_u8));
422
423 Ok(())
424 }
425
426 #[test]
427 fn it_rejects_ids_out_of_range() {
428 assert!(serde_json::from_str::<BoxID>(&MAX_BOXES_COUNT.to_string()).is_err());
429 assert!(serde_json::from_str::<GoalID>(&MAX_BOXES_COUNT.to_string()).is_err());
430 assert!(serde_json::from_str::<PusherID>(&MAX_PUSHERS_COUNT.to_string()).is_err());
431
432 assert!(serde_json::from_str::<BoxID>("65536").is_err());
433 assert!(serde_json::from_str::<BoxID>("-1").is_err());
434 }
435
436 #[test]
437 fn it_accepts_the_largest_legal_id() -> Result<(), Box<dyn Error>> {
438 assert_eq!(
439 serde_json::from_str::<BoxID>(&BoxID::MAX.to_string())?,
440 BoxID::try_from(BoxID::MAX)?
441 );
442 assert_eq!(
443 serde_json::from_str::<PusherID>(&PusherID::MAX.to_string())?,
444 PusherID::try_from(PusherID::MAX)?
445 );
446
447 Ok(())
448 }
449 }
450}