1use serde::{Deserialize, Serialize};
4use std::cmp::Ordering;
5use std::convert::TryFrom;
6use std::fmt;
7use std::fmt::{Display, Formatter};
8
9const U53_MAX: u64 = 9_007_199_254_740_991;
10#[allow(clippy::as_conversions)]
11const I54_MAX: i64 = U53_MAX as i64;
12const I54_MIN: i64 = -9_007_199_254_740_991;
13
14#[derive(Debug, PartialEq, Eq)]
19pub struct TryFromIntError(());
20
21impl Display for TryFromIntError {
23 fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
24 write!(fmt, "Integer type conversion fail")
25 }
26}
27
28macro_rules! truncated_type {
29 ($truncated: ident, $untruncated: ident, $untruncated_str: expr, $min: expr, $max: expr, $doc: expr) => {
30 #[doc = $doc]
31 #[derive(
32 Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Hash,
33 )]
34 #[serde(try_from = $untruncated_str)]
35 pub struct $truncated($untruncated);
36
37 impl $truncated {
38 pub const MIN: $truncated = $truncated($min);
40
41 pub const MAX: $truncated = $truncated($max);
43 }
44
45 impl fmt::Debug for $truncated {
46 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
47 fmt::Debug::fmt(&self.0, fmt)
48 }
49 }
50
51 impl fmt::Display for $truncated {
52 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
53 fmt::Display::fmt(&self.0, fmt)
54 }
55 }
56
57 impl TryFrom<$untruncated> for $truncated {
58 type Error = TryFromIntError;
59
60 #[allow(unused_comparisons)]
61 fn try_from(value: $untruncated) -> Result<Self, Self::Error> {
62 if !($min..=$max).contains(&value) {
63 return Err(TryFromIntError(()));
64 }
65
66 Ok($truncated(value))
67 }
68 }
69
70 impl From<$truncated> for $untruncated {
71 fn from(value: $truncated) -> $untruncated {
72 value.0
73 }
74 }
75
76 impl PartialEq<$untruncated> for $truncated {
77 fn eq(&self, other: &$untruncated) -> bool {
78 self.0 == *other
79 }
80 }
81
82 impl PartialOrd<$untruncated> for $truncated {
83 fn partial_cmp(&self, other: &$untruncated) -> Option<Ordering> {
84 Some(self.0.cmp(other))
85 }
86 }
87 };
88}
89
90macro_rules! impl_truncated_type_from {
91 ($from: ident, $into: ident) => {
92 impl From<$into> for $from {
93 fn from(value: $into) -> $from {
94 $from(value.into())
95 }
96 }
97
98 impl TryFrom<$from> for $into {
99 type Error = TryFromIntError;
100
101 #[allow(unused_comparisons)]
102 fn try_from(value: $from) -> Result<$into, Self::Error> {
103 if value.0 < $into::MIN.into() || value.0 > $into::MAX.into() {
104 return Err(TryFromIntError(()));
105 }
106
107 #[allow(clippy::as_conversions)]
108 Ok(value.0 as $into)
109 }
110 }
111 };
112}
113
114truncated_type!(
115 U53,
116 u64,
117 "u64",
118 0,
119 U53_MAX,
120 "The 53-bit unsigned integer type. Purpose of this type is to mimic JavaScript's integer type."
121);
122impl_truncated_type_from!(U53, u32);
123impl_truncated_type_from!(U53, u16);
124impl_truncated_type_from!(U53, u8);
125
126truncated_type!(
127 I54,
128 i64,
129 "i64",
130 I54_MIN,
131 I54_MAX,
132 "The 54-bit signed integer type. Purpose of this type is to mimic JavaScript's integer type."
133);
134impl_truncated_type_from!(I54, i32);
135impl_truncated_type_from!(I54, i16);
136impl_truncated_type_from!(I54, i8);
137
138#[inline]
140#[must_use]
141pub fn usize_from_u53_saturated(value: U53) -> usize {
142 usize_from_u64_saturated(value.0)
143}
144
145#[allow(clippy::as_conversions)]
147#[inline]
148#[must_use]
149pub fn usize_from_u64_saturated(value: u64) -> usize {
150 std::cmp::min(value, usize::MAX as u64) as usize
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
159 fn test_i54_init() {
160 assert_eq!(I54::try_from(I54_MAX).unwrap(), I54_MAX);
161 }
162
163 #[test]
164 fn test_i54_overflow() {
165 assert_eq!(I54::try_from(I54_MAX + 1), Err(TryFromIntError(())));
166 }
167
168 #[test]
169 fn test_i54_underflow() {
170 assert_eq!(I54::try_from(I54_MIN - 1), Err(TryFromIntError(())));
171 }
172
173 #[test]
174 fn test_i64_to_i54() {
175 assert_eq!(I54::try_from(i64::MAX), Err(TryFromIntError(())));
176 }
177
178 #[test]
179 fn test_i54_to_i64() {
180 assert_eq!(i64::from(I54::try_from(I54_MAX).unwrap()), I54_MAX);
181 }
182
183 #[test]
184 fn test_i54_to_i32() {
185 assert_eq!(i32::try_from(I54::from(i32::MAX)).unwrap(), i32::MAX);
186 }
187
188 #[test]
189 #[allow(clippy::as_conversions)]
190 fn test_i32_to_i54() {
191 assert_eq!(I54::from(i32::MAX), i64::from(i32::MAX));
192 }
193
194 #[test]
196 fn test_u53_init() {
197 assert_eq!(U53::try_from(U53_MAX).unwrap(), U53_MAX);
198 }
199
200 #[test]
201 fn test_u53_overflow() {
202 assert_eq!(U53::try_from(U53_MAX + 1), Err(TryFromIntError(())));
203 }
204
205 #[test]
206 fn test_u64_to_u53() {
207 assert_eq!(U53::try_from(u64::MAX), Err(TryFromIntError(())));
208 }
209
210 #[test]
211 fn test_u53_to_u64() {
212 assert_eq!(u64::from(U53::try_from(U53_MAX).unwrap()), U53_MAX);
213 }
214
215 #[test]
216 fn test_u53_to_u32() {
217 assert_eq!(u32::try_from(U53::from(u32::MAX)).unwrap(), u32::MAX);
218 }
219
220 #[test]
221 #[allow(clippy::as_conversions)]
222 fn test_u32_to_u53() {
223 assert_eq!(U53::from(u32::MAX), u64::from(u32::MAX));
224 }
225
226 #[test]
227 fn test_order() {
228 assert!(U53::from(u32::MAX) < u64::MAX);
229 }
230
231 #[test]
232 fn test_serde_serialize() {
233 #[derive(Serialize)]
234 struct Person {
235 age: I54,
236 }
237
238 let j = serde_json::to_string(&Person { age: I54::from(12) }).unwrap();
239 assert_eq!(j, r##"{"age":12}"##);
240 }
241
242 #[test]
243 fn test_serde_deserialize() {
244 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
245 struct Person {
246 age: I54,
247 }
248
249 let j = r##"{"age":14}"##;
250 assert_eq!(
251 serde_json::from_str::<Person>(j).unwrap(),
252 Person { age: I54::from(14) }
253 );
254 }
255
256 #[test]
257 fn test_serde_deserialize_overflow() {
258 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
259 struct Person {
260 age: I54,
261 }
262
263 let j = format!(r##"{{"age":{}}}"##, I54_MAX + 1);
264 assert!(serde_json::from_str::<Person>(j.as_str()).is_err());
265 }
266
267 #[test]
268 fn test_serde_deserialize_underflow() {
269 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
270 struct Person {
271 age: I54,
272 }
273
274 let j = format!(r##"{{"age":{}}}"##, I54_MIN - 1);
275 assert!(serde_json::from_str::<Person>(j.as_str()).is_err());
276 }
277
278 #[test]
279 fn test_formatter_flags() {
280 let value: I54 = 125.into();
281
282 assert_eq!(format!("{:>+8}", value), " +125");
284 }
285
286 #[test]
287 fn i54_max() {
288 assert_eq!(I54_MAX, i64::from(I54::MAX));
289 }
290
291 #[test]
292 fn i54_min() {
293 assert_eq!(I54_MIN, i64::from(I54::MIN));
294 }
295
296 #[test]
297 fn u53_max() {
298 assert_eq!(U53_MAX, u64::from(U53::MAX));
299 }
300
301 #[test]
302 fn u53_min() {
303 assert_eq!(0, u64::from(U53::MIN));
304 }
305}