1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Clone, Copy, Debug)]
6pub struct Number(Repr);
7
8#[derive(Clone, Copy, Debug)]
9enum Repr {
10 I64(i64),
11 U64(u64),
12 F64(f64),
13}
14
15impl Number {
16 #[must_use]
18 pub fn from_f64(value: f64) -> Option<Self> {
19 value.is_finite().then_some(Self(Repr::F64(value)))
20 }
21
22 #[must_use]
23 pub fn is_i64(&self) -> bool {
24 self.as_i64().is_some()
25 }
26
27 #[must_use]
28 pub const fn is_u64(&self) -> bool {
29 matches!(self.0, Repr::U64(_))
30 }
31
32 #[must_use]
33 pub const fn is_f64(&self) -> bool {
34 matches!(self.0, Repr::F64(_))
35 }
36
37 #[must_use]
38 pub fn as_i64(&self) -> Option<i64> {
39 match self.0 {
40 Repr::I64(value) => Some(value),
41 Repr::U64(value) => i64::try_from(value).ok(),
42 Repr::F64(_) => None,
43 }
44 }
45
46 #[must_use]
47 pub fn as_u64(&self) -> Option<u64> {
48 match self.0 {
49 Repr::I64(value) => u64::try_from(value).ok(),
50 Repr::U64(value) => Some(value),
51 Repr::F64(_) => None,
52 }
53 }
54
55 #[must_use]
56 #[allow(clippy::cast_precision_loss)]
57 pub fn as_f64(&self) -> Option<f64> {
58 match self.0 {
59 Repr::I64(value) => Some(value as f64),
60 Repr::U64(value) => Some(value as f64),
61 Repr::F64(value) => Some(value),
62 }
63 }
64}
65
66impl PartialEq for Number {
67 fn eq(&self, other: &Self) -> bool {
68 match (self.0, other.0) {
69 (Repr::I64(left), Repr::I64(right)) => left == right,
70 (Repr::U64(left), Repr::U64(right)) => left == right,
71 (Repr::F64(left), Repr::F64(right)) => left == right,
72 _ => false,
73 }
74 }
75}
76
77impl Eq for Number {}
78
79impl fmt::Display for Number {
80 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self.0 {
82 Repr::I64(value) => value.fmt(formatter),
83 Repr::U64(value) => value.fmt(formatter),
84 Repr::F64(value) => formatter.write_str(zmij::Buffer::new().format_finite(value)),
85 }
86 }
87}
88
89impl Serialize for Number {
90 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
91 match self.0 {
92 Repr::I64(value) => serializer.serialize_i64(value),
93 Repr::U64(value) => serializer.serialize_u64(value),
94 Repr::F64(value) => serializer.serialize_f64(value),
95 }
96 }
97}
98
99impl<'de> Deserialize<'de> for Number {
100 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
101 struct NumberVisitor;
102
103 impl serde::de::Visitor<'_> for NumberVisitor {
104 type Value = Number;
105
106 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
107 formatter.write_str("a JSON number")
108 }
109
110 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
111 Ok(Number::from(value))
112 }
113
114 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
115 Ok(Number::from(value))
116 }
117
118 fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
119 Number::from_f64(value).ok_or_else(|| E::custom("non-finite JSON number"))
120 }
121 }
122
123 deserializer.deserialize_any(NumberVisitor)
124 }
125}
126
127macro_rules! from_signed {
128 ($($type:ty),+ $(,)?) => {
129 $(
130 impl From<$type> for Number {
131 fn from(value: $type) -> Self {
132 let value = i64::try_from(value).expect("signed primitive fits in i64");
133 if value < 0 {
134 Self(Repr::I64(value))
135 } else {
136 Self(Repr::U64(
137 u64::try_from(value).expect("nonnegative i64 fits in u64"),
138 ))
139 }
140 }
141 }
142 )+
143 };
144}
145
146macro_rules! from_unsigned {
147 ($($type:ty),+ $(,)?) => {
148 $(
149 impl From<$type> for Number {
150 fn from(value: $type) -> Self {
151 Self(Repr::U64(u64::try_from(value).expect("unsigned primitive fits in u64")))
152 }
153 }
154 )+
155 };
156}
157
158from_signed!(i8, i16, i32, i64, isize);
159from_unsigned!(u8, u16, u32, u64, usize);