1use std::fmt;
2use std::str::FromStr;
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub struct Number(pub(crate) String);
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct InvalidNumber(String);
27
28impl fmt::Display for InvalidNumber {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "invalid JSON number: {}", self.0)
31 }
32}
33
34impl std::error::Error for InvalidNumber {}
35
36impl Number {
37 pub fn as_str(&self) -> &str {
39 &self.0
40 }
41
42 pub fn as_i64(&self) -> Option<i64> {
44 self.0.parse().ok()
45 }
46
47 pub fn as_u64(&self) -> Option<u64> {
49 self.0.parse().ok()
50 }
51
52 pub fn as_f64(&self) -> Option<f64> {
58 self.0.parse().ok()
59 }
60
61 pub fn is_integer(&self) -> bool {
63 !self.0.contains('.') && !self.0.contains('e') && !self.0.contains('E')
64 }
65
66 pub(crate) fn to_serde_json_number(&self) -> serde_json::Number {
69 self.0.parse().expect("Number string validated by serde_json at construction")
70 }
71}
72
73impl FromStr for Number {
74 type Err = InvalidNumber;
75
76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 s.parse::<serde_json::Number>()
80 .map(|_| Self(s.to_owned()))
81 .map_err(|_| InvalidNumber(s.to_owned()))
82 }
83}
84
85impl TryFrom<f64> for Number {
86 type Error = InvalidNumber;
87
88 fn try_from(value: f64) -> Result<Self, Self::Error> {
89 serde_json::Number::from_f64(value)
91 .map(|n| Self(n.to_string()))
92 .ok_or_else(|| InvalidNumber(value.to_string()))
93 }
94}
95
96impl From<i64> for Number {
97 fn from(value: i64) -> Self { Self(value.to_string()) }
98}
99
100impl From<u64> for Number {
101 fn from(value: u64) -> Self { Self(value.to_string()) }
102}
103
104impl From<i32> for Number {
105 fn from(value: i32) -> Self { Self(value.to_string()) }
106}
107
108impl From<u32> for Number {
109 fn from(value: u32) -> Self { Self(value.to_string()) }
110}
111
112impl fmt::Display for Number {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 f.write_str(&self.0)
115 }
116}
117
118impl serde::Serialize for Number {
119 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
120 self.to_serde_json_number().serialize(serializer)
121 }
122}
123
124impl<'de> serde::Deserialize<'de> for Number {
125 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
126 serde_json::Number::deserialize(deserializer).map(|n| Self(n.to_string()))
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn parse_valid() {
136 for s in ["0", "-0", "1", "-1", "42", "3.14", "-3.14", "1e10", "1E10",
137 "1.5e-3", "1.5E+3", "0.0", "99999999999999999999"] {
138 assert!(s.parse::<Number>().is_ok(), "expected valid: {s}");
139 }
140 }
141
142 #[test]
143 fn parse_invalid() {
144 for s in ["", "nan", "NaN", "inf", "Infinity", "-inf",
145 "1.", ".5", "1e", "1e+", "01", "--1", "+1"] {
146 assert!(s.parse::<Number>().is_err(), "expected invalid: {s}");
147 }
148 }
149
150 #[test]
151 fn roundtrip_string() {
152 for s in ["42", "-3.14", "1e100", "1E10", "99999999999999999999"] {
153 let n: Number = s.parse().unwrap();
154 assert_eq!(n.as_str(), s, "roundtrip failed for {s}");
155 }
156 }
157
158 #[test]
159 fn from_f64_rejects_non_finite() {
160 assert!(Number::try_from(f64::NAN).is_err());
161 assert!(Number::try_from(f64::INFINITY).is_err());
162 assert!(Number::try_from(f64::NEG_INFINITY).is_err());
163 }
164
165 #[test]
166 fn from_f64_finite() {
167 let n = Number::try_from(3.14_f64).unwrap();
168 assert_eq!(n.as_str(), "3.14");
169 }
170
171 #[test]
172 fn from_integers() {
173 assert_eq!(Number::from(42i64).as_str(), "42");
174 assert_eq!(Number::from(u64::MAX).as_str(), "18446744073709551615");
175 assert_eq!(Number::from(-1i64).as_str(), "-1");
176 }
177
178 #[test]
179 fn as_accessors() {
180 let n: Number = "42".parse().unwrap();
181 assert_eq!(n.as_i64(), Some(42));
182 assert_eq!(n.as_u64(), Some(42));
183
184 let n: Number = "-5".parse().unwrap();
185 assert_eq!(n.as_i64(), Some(-5));
186 assert_eq!(n.as_u64(), None);
187
188 let n: Number = "3.14".parse().unwrap();
189 assert_eq!(n.as_i64(), None);
190 assert!((n.as_f64().unwrap() - 3.14).abs() < 1e-10);
191 }
192
193 #[test]
194 fn is_integer() {
195 assert!("42".parse::<Number>().unwrap().is_integer());
196 assert!(!"3.14".parse::<Number>().unwrap().is_integer());
197 assert!(!"1e10".parse::<Number>().unwrap().is_integer());
198 }
199}