sokoban_elements/sokoban_plus/
plus_id.rs1use std::fmt::{Display, Formatter};
2use std::num::NonZeroU16;
3
4use crate::{PieceIdError, PieceIdErrorKind};
5
6#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(
17 feature = "serde",
18 derive(serde::Serialize, serde::Deserialize),
19 serde(try_from = "u16", into = "u16")
20)]
21pub struct PlusID(NonZeroU16);
22
23impl PlusID {
24 pub fn new(id: u16) -> Result<Self, PieceIdError> { id.try_into() }
25}
26
27impl Display for PlusID {
28 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }
29}
30
31impl From<PlusID> for u16 {
34 fn from(value: PlusID) -> Self { value.0.get() }
35}
36
37impl From<PlusID> for usize {
38 fn from(value: PlusID) -> Self { value.0.get().into() }
39}
40
41macro_rules! implement_plus_id_construct_from_int {
42 ( u8 ) => {
43 impl TryFrom<u8> for PlusID {
44 type Error = PieceIdError;
45 fn try_from(value: u8) -> Result<Self, Self::Error> {
46 Ok(Self(NonZeroU16::try_from(u16::from(value)).map_err(|_| {
47 PieceIdError {
48 kind: PieceIdErrorKind::InvalidPlusID,
49 value: value.to_string(),
50 }
51 })?))
52 }
53 }
54 };
55
56 ( u16) => {
57 impl TryFrom<u16> for PlusID {
58 type Error = PieceIdError;
59 fn try_from(value: u16) -> Result<Self, Self::Error> {
60 Ok(Self(NonZeroU16::try_from(value).map_err(|_| {
61 PieceIdError {
62 kind: PieceIdErrorKind::InvalidPlusID,
63 value: value.to_string(),
64 }
65 })?))
66 }
67 }
68 };
69
70 ( $IntTy:ty ) => {
71 impl TryFrom<$IntTy> for PlusID {
72 type Error = PieceIdError;
73 fn try_from(value: $IntTy) -> Result<Self, Self::Error> {
74 Ok(Self(
75 NonZeroU16::try_from(u16::try_from(value).map_err(|_| {
76 PieceIdError {
77 kind: PieceIdErrorKind::InvalidPlusID,
78 value: value.to_string(),
79 }
80 })?)
81 .map_err(|_| {
82 PieceIdError {
83 kind: PieceIdErrorKind::InvalidPlusID,
84 value: value.to_string(),
85 }
86 })?,
87 ))
88 }
89 }
90 };
91}
92
93implement_plus_id_construct_from_int!(u8);
94implement_plus_id_construct_from_int!(u16);
95implement_plus_id_construct_from_int!(u32);
96implement_plus_id_construct_from_int!(u64);
97implement_plus_id_construct_from_int!(usize);
98implement_plus_id_construct_from_int!(i8);
99implement_plus_id_construct_from_int!(i16);
100implement_plus_id_construct_from_int!(i32);
101implement_plus_id_construct_from_int!(i64);
102implement_plus_id_construct_from_int!(isize);
103
104impl PartialEq<u8> for PlusID {
111 fn eq(&self, other: &u8) -> bool { self.0.get() == u16::from(*other) }
112}
113
114impl PartialEq<u16> for PlusID {
115 fn eq(&self, other: &u16) -> bool { self.0.get() == *other }
116}
117
118impl PartialEq<u32> for PlusID {
119 fn eq(&self, other: &u32) -> bool { u32::from(self.0.get()) == *other }
120}
121
122impl PartialEq<u64> for PlusID {
123 fn eq(&self, other: &u64) -> bool { u64::from(self.0.get()) == *other }
124}
125
126impl PartialEq<usize> for PlusID {
127 fn eq(&self, other: &usize) -> bool { usize::from(self.0.get()) == *other }
128}
129
130impl PartialEq<i8> for PlusID {
131 fn eq(&self, other: &i8) -> bool { i32::from(self.0.get()) == i32::from(*other) }
132}
133
134impl PartialEq<i16> for PlusID {
135 fn eq(&self, other: &i16) -> bool { i32::from(self.0.get()) == i32::from(*other) }
136}
137
138impl PartialEq<i32> for PlusID {
139 fn eq(&self, other: &i32) -> bool { i32::from(self.0.get()) == *other }
140}
141
142impl PartialEq<i64> for PlusID {
143 fn eq(&self, other: &i64) -> bool { i64::from(self.0.get()) == *other }
144}
145
146impl PartialEq<isize> for PlusID {
147 fn eq(&self, other: &isize) -> bool {
148 match isize::try_from(self.0.get()) {
149 Ok(self_isize) => self_isize == *other,
150 Err(_) => false,
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_plus_id_can_be_constructed_from_integers() -> Result<(), PieceIdError> {
161 assert!(PlusID::new(42)? == 42_u8);
162
163 assert!(PlusID::try_from(42_u8)? == 42_u8);
164 assert!(PlusID::try_from(42_u16)? == 42_u8);
165 assert!(PlusID::try_from(42_u32)? == 42_u8);
166 assert!(PlusID::try_from(42_u64)? == 42_u8);
167 assert!(PlusID::try_from(42_usize)? == 42_u8);
168
169 assert!(PlusID::try_from(42_i8)? == 42_u8);
170 assert!(PlusID::try_from(42_i16)? == 42_u8);
171 assert!(PlusID::try_from(42_i32)? == 42_u8);
172 assert!(PlusID::try_from(42_i64)? == 42_u8);
173 assert!(PlusID::try_from(42_isize)? == 42_u8);
174
175 assert!(PlusID::try_from(-1_i32).is_err());
176
177 Ok(())
178 }
179
180 #[test]
181 fn test_plus_id_is_value_constrained() {
182 assert!(PlusID::new(0).is_err());
183 assert!(PlusID::try_from(0_u8).is_err());
184 assert!(PlusID::try_from(usize::from(u16::MAX) + 1).is_err());
185 }
186
187 #[test]
188 fn test_plus_id_is_comparable_to_integers() -> Result<(), PieceIdError> {
189 assert!(PlusID::try_from(1_u8)? == 1_u8);
190 assert!(PlusID::try_from(1_u8)? == 1_u16);
191 assert!(PlusID::try_from(1_u8)? == 1_u32);
192 assert!(PlusID::try_from(1_u8)? == 1_u64);
193 assert!(PlusID::try_from(1_u8)? == 1_usize);
194 assert!(PlusID::try_from(1_u8)? == 1_i8);
195 assert!(PlusID::try_from(1_u8)? == 1_i16);
196 assert!(PlusID::try_from(1_u8)? == 1_i32);
197 assert!(PlusID::try_from(1_u8)? == 1_i64);
198 assert!(PlusID::try_from(1_u8)? == 1_isize);
199
200 assert!(PlusID::try_from(1_u8)? != 2_u8);
201 assert!(PlusID::try_from(1_u8)? != 2_u16);
202 assert!(PlusID::try_from(1_u8)? != 2_u32);
203 assert!(PlusID::try_from(1_u8)? != 2_u64);
204 assert!(PlusID::try_from(1_u8)? != 2_usize);
205 assert!(PlusID::try_from(1_u8)? != 2_i8);
206 assert!(PlusID::try_from(1_u8)? != 2_i16);
207 assert!(PlusID::try_from(1_u8)? != 2_i32);
208 assert!(PlusID::try_from(1_u8)? != 2_i64);
209 assert!(PlusID::try_from(1_u8)? != 2_isize);
210
211 Ok(())
212 }
213
214 #[cfg(feature = "serde")]
215 mod test_plus_id_serde {
216 use std::error::Error;
217
218 use super::*;
219
220 #[test]
221 fn it_serializes_as_a_bare_integer() -> Result<(), Box<dyn Error>> {
222 assert_eq!(serde_json::to_string(&PlusID::new(42)?)?, "42");
223 assert_eq!(serde_json::from_str::<PlusID>("42")?, PlusID::new(42)?);
224
225 Ok(())
226 }
227
228 #[test]
229 fn it_rejects_ids_out_of_range() {
230 assert!(serde_json::from_str::<PlusID>("0").is_err());
231 assert!(serde_json::from_str::<PlusID>("65536").is_err());
232 assert!(serde_json::from_str::<PlusID>("-1").is_err());
233 }
234 }
235}