use std::fmt;
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Number(pub(crate) String);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InvalidNumber(String);
impl fmt::Display for InvalidNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid JSON number: {}", self.0)
}
}
impl std::error::Error for InvalidNumber {}
impl Number {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn as_i64(&self) -> Option<i64> {
self.0.parse().ok()
}
pub fn as_u64(&self) -> Option<u64> {
self.0.parse().ok()
}
pub fn as_f64(&self) -> Option<f64> {
self.0.parse().ok()
}
pub fn is_integer(&self) -> bool {
!self.0.contains('.') && !self.0.contains('e') && !self.0.contains('E')
}
pub(crate) fn to_serde_json_number(&self) -> serde_json::Number {
self.0.parse().expect("Number string validated by serde_json at construction")
}
}
impl FromStr for Number {
type Err = InvalidNumber;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<serde_json::Number>()
.map(|_| Self(s.to_owned()))
.map_err(|_| InvalidNumber(s.to_owned()))
}
}
impl TryFrom<f64> for Number {
type Error = InvalidNumber;
fn try_from(value: f64) -> Result<Self, Self::Error> {
serde_json::Number::from_f64(value)
.map(|n| Self(n.to_string()))
.ok_or_else(|| InvalidNumber(value.to_string()))
}
}
impl From<i64> for Number {
fn from(value: i64) -> Self { Self(value.to_string()) }
}
impl From<u64> for Number {
fn from(value: u64) -> Self { Self(value.to_string()) }
}
impl From<i32> for Number {
fn from(value: i32) -> Self { Self(value.to_string()) }
}
impl From<u32> for Number {
fn from(value: u32) -> Self { Self(value.to_string()) }
}
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl serde::Serialize for Number {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.to_serde_json_number().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Number {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
serde_json::Number::deserialize(deserializer).map(|n| Self(n.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_valid() {
for s in ["0", "-0", "1", "-1", "42", "3.14", "-3.14", "1e10", "1E10",
"1.5e-3", "1.5E+3", "0.0", "99999999999999999999"] {
assert!(s.parse::<Number>().is_ok(), "expected valid: {s}");
}
}
#[test]
fn parse_invalid() {
for s in ["", "nan", "NaN", "inf", "Infinity", "-inf",
"1.", ".5", "1e", "1e+", "01", "--1", "+1"] {
assert!(s.parse::<Number>().is_err(), "expected invalid: {s}");
}
}
#[test]
fn roundtrip_string() {
for s in ["42", "-3.14", "1e100", "1E10", "99999999999999999999"] {
let n: Number = s.parse().unwrap();
assert_eq!(n.as_str(), s, "roundtrip failed for {s}");
}
}
#[test]
fn from_f64_rejects_non_finite() {
assert!(Number::try_from(f64::NAN).is_err());
assert!(Number::try_from(f64::INFINITY).is_err());
assert!(Number::try_from(f64::NEG_INFINITY).is_err());
}
#[test]
fn from_f64_finite() {
let n = Number::try_from(3.14_f64).unwrap();
assert_eq!(n.as_str(), "3.14");
}
#[test]
fn from_integers() {
assert_eq!(Number::from(42i64).as_str(), "42");
assert_eq!(Number::from(u64::MAX).as_str(), "18446744073709551615");
assert_eq!(Number::from(-1i64).as_str(), "-1");
}
#[test]
fn as_accessors() {
let n: Number = "42".parse().unwrap();
assert_eq!(n.as_i64(), Some(42));
assert_eq!(n.as_u64(), Some(42));
let n: Number = "-5".parse().unwrap();
assert_eq!(n.as_i64(), Some(-5));
assert_eq!(n.as_u64(), None);
let n: Number = "3.14".parse().unwrap();
assert_eq!(n.as_i64(), None);
assert!((n.as_f64().unwrap() - 3.14).abs() < 1e-10);
}
#[test]
fn is_integer() {
assert!("42".parse::<Number>().unwrap().is_integer());
assert!(!"3.14".parse::<Number>().unwrap().is_integer());
assert!(!"1e10".parse::<Number>().unwrap().is_integer());
}
}