1#[cfg(feature = "serde")]
14use serde::de::{self, Visitor};
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Deserializer, Serialize, Serializer};
17
18#[derive(Debug, Clone, Copy)]
25pub enum Number {
26 U64(u64),
27 I64(i64),
28 F64(f64),
29}
30
31impl Number {
32 pub(crate) fn sentinel(self) -> Option<&'static str> {
39 match self {
40 Self::F64(v) if v.is_nan() => Some("nan"),
41 Self::F64(v) if v == f64::INFINITY => Some("inf"),
42 Self::F64(v) if v == f64::NEG_INFINITY => Some("neg_inf"),
43 _ => None,
44 }
45 }
46
47 pub(crate) fn from_sentinel(s: &str) -> Option<Self> {
50 match s {
51 "nan" => Some(Self::F64(f64::NAN)),
52 "inf" => Some(Self::F64(f64::INFINITY)),
53 "neg_inf" => Some(Self::F64(f64::NEG_INFINITY)),
54 _ => None,
55 }
56 }
57}
58
59#[cfg(feature = "serde")]
60impl Serialize for Number {
61 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
62 if let Some(sentinel) = self.sentinel() {
63 return serializer.serialize_str(sentinel);
64 }
65 match *self {
66 Self::U64(v) => serializer.serialize_u64(v),
67 Self::I64(v) => serializer.serialize_i64(v),
68 Self::F64(v) => serializer.serialize_f64(v),
69 }
70 }
71}
72
73#[cfg(feature = "serde")]
74impl<'de> Deserialize<'de> for Number {
75 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
76 struct NumberVisitor;
77
78 impl<'de> Visitor<'de> for NumberVisitor {
79 type Value = Number;
80
81 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82 f.write_str("a number")
83 }
84
85 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Number, E> {
86 Ok(Number::U64(v))
87 }
88
89 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Number, E> {
90 Ok(Number::I64(v))
91 }
92
93 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Number, E> {
94 Ok(Number::F64(v))
95 }
96
97 fn visit_str<E: de::Error>(self, v: &str) -> Result<Number, E> {
98 Number::from_sentinel(v).ok_or_else(|| {
99 de::Error::custom(format!("expected a number or numeric sentinel, got {v:?}"))
100 })
101 }
102 }
103
104 deserializer.deserialize_any(NumberVisitor)
105 }
106}
107
108macro_rules! try_cast {
109 ($v:ident :$T:ty => $U:ty) => {{
110 let c = $v as $U;
111 if c as $T == $v { Some(c) } else { None }
112 }};
113}
114
115macro_rules! int {
116 ($f:ident, $T:ty) => {
117 pub fn $f(self) -> Option<$T> {
118 match self {
119 Self::U64(v) => v.try_into().ok(),
120 Self::I64(v) => v.try_into().ok(),
121 Self::F64(v) => try_cast!(v:f64 => $T),
122 }
123 }
124 }
125}
126
127macro_rules! float {
128 ($f:ident, $T:ty) => {
129 pub fn $f(self) -> Option<$T> {
130 match self {
131 Self::U64(v) => try_cast!(v:u64 => $T),
132 Self::I64(v) => try_cast!(v:i64 => $T),
133 Self::F64(v) if v.is_nan() => Some(v as $T),
136 Self::F64(v) => try_cast!(v:f64 => $T),
137 }
138 }
139 }
140}
141
142impl Number {
143 int!(as_u8, u8);
144 int!(as_u16, u16);
145 int!(as_u32, u32);
146 int!(as_u64, u64);
147 int!(as_usize, usize);
148
149 int!(as_i8, i8);
150 int!(as_i16, i16);
151 int!(as_i32, i32);
152 int!(as_i64, i64);
153 int!(as_isize, isize);
154
155 float!(as_f32, f32);
156 float!(as_f64, f64);
157}
158
159macro_rules! from {
160 ($T:ty => $variant:ident) => {
161 impl From<$T> for Number {
162 fn from(v: $T) -> Self {
163 Self::$variant(v.into())
164 }
165 }
166 };
167 ($($T:ty => $variant:ident),+ $(,)?) => {
168 $(from!($T => $variant);)+
169 }
170}
171
172from!(
173 u64 => U64,
174 u32 => U64,
175 u16 => U64,
176 u8 => U64,
177 i64 => I64,
178 i32 => I64,
179 i16 => I64,
180 i8 => I64,
181 f32 => F64,
182 f64 => F64,
183);
184
185impl From<usize> for Number {
186 fn from(v: usize) -> Self {
187 Self::U64(v.try_into().unwrap())
188 }
189}
190
191macro_rules! try_from {
192 ($T:ty => $f:ident) => {
193 impl TryFrom<Number> for $T {
194 type Error = ();
195 fn try_from(number: Number) -> Result<$T, Self::Error> {
196 number.$f().ok_or(())
197 }
198 }
199 };
200 ($($T:ty => $f:ident),+ $(,)?) => {
201 $(try_from!($T => $f);)+
202 }
203}
204
205try_from!(
206 u64 => as_u64,
207 u32 => as_u32,
208 u16 => as_u16,
209 u8 => as_u8,
210 usize => as_usize,
211
212 i64 => as_i64,
213 i32 => as_i32,
214 i16 => as_i16,
215 i8 => as_i8,
216 isize => as_isize,
217
218 f32 => as_f32,
219 f64 => as_f64,
220);
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn integer_accessors_range_check() {
228 assert_eq!(Number::U64(255).as_u8(), Some(255));
229 assert_eq!(Number::U64(256).as_u8(), None);
230 assert_eq!(Number::I64(-1).as_u8(), None);
231 assert_eq!(Number::I64(-5).as_i8(), Some(-5));
232 assert_eq!(Number::I64(-129).as_i8(), None);
233 }
234
235 #[test]
236 fn float_to_integer_requires_integral_value() {
237 assert_eq!(Number::F64(2.0).as_u32(), Some(2));
238 assert_eq!(Number::F64(2.5).as_u32(), None);
239 assert_eq!(Number::F64(-1.0).as_u32(), None);
240 }
241
242 #[test]
244 fn nan_survives_float_accessors() {
245 assert_eq!(
246 Number::F64(f64::NAN).as_f64().map(f64::is_nan),
247 Some(true),
248 "as_f64 dropped a NaN"
249 );
250 assert_eq!(
251 Number::F64(f64::NAN).as_f32().map(f32::is_nan),
252 Some(true),
253 "as_f32 dropped a NaN"
254 );
255 assert_eq!(Number::F64(f64::INFINITY).as_f64(), Some(f64::INFINITY));
257 }
258
259 #[test]
260 fn nan_round_trips_through_try_from() {
261 let back = f64::try_from(Number::F64(f64::NAN)).expect("NaN must convert back to f64");
262 assert!(back.is_nan());
263 }
264
265 #[test]
266 fn try_from_surfaces_out_of_range() {
267 assert!(u8::try_from(Number::U64(300)).is_err());
268 assert_eq!(u16::try_from(Number::U64(300)).unwrap(), 300);
269 assert!(usize::try_from(Number::I64(-1)).is_err());
270 }
271
272 #[cfg(feature = "disk")]
273 #[test]
274 fn non_finite_floats_round_trip_via_sentinels() {
275 for (value, sentinel) in [
276 (f64::NAN, "\"nan\""),
277 (f64::INFINITY, "\"inf\""),
278 (f64::NEG_INFINITY, "\"neg_inf\""),
279 ] {
280 let json = serde_json::to_string(&Number::F64(value)).unwrap();
281 assert_eq!(json, sentinel);
282
283 let back: Number = serde_json::from_str(&json).unwrap();
284 match back {
285 Number::F64(v) if value.is_nan() => assert!(v.is_nan()),
286 Number::F64(v) => assert_eq!(v, value),
287 other => panic!("expected F64, got {other:?}"),
288 }
289 }
290 }
291
292 #[cfg(feature = "disk")]
293 #[test]
294 fn finite_floats_serialize_as_json_numbers() {
295 assert_eq!(serde_json::to_string(&Number::F64(1.5)).unwrap(), "1.5");
296 assert_eq!(serde_json::to_string(&Number::U64(7)).unwrap(), "7");
297 assert_eq!(serde_json::to_string(&Number::I64(-7)).unwrap(), "-7");
298 }
299
300 #[cfg(feature = "disk")]
301 #[test]
302 fn json_numbers_select_matching_variant() {
303 assert!(matches!(serde_json::from_str("7").unwrap(), Number::U64(7)));
306 assert!(matches!(
307 serde_json::from_str("-7").unwrap(),
308 Number::I64(-7)
309 ));
310 match serde_json::from_str("1.5").unwrap() {
311 Number::F64(v) => assert_eq!(v, 1.5),
312 other => panic!("expected F64, got {other:?}"),
313 }
314 }
315
316 #[cfg(feature = "disk")]
317 #[test]
318 fn non_sentinel_string_is_rejected() {
319 let err = serde_json::from_str::<Number>("\"bogus\"").unwrap_err();
320 assert!(
321 err.to_string().contains("numeric sentinel"),
322 "unexpected error message: {err}"
323 );
324 }
325}