use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use serde::de::{Error as DeserializeError, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
const MAX_SAFE_INTEGER: u64 = (1 << 53) - 1;
pub mod i64 {
use super::*;
pub fn serialize<S: Serializer>(value: &i64, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<i64, D::Error> {
struct SignedVisitor;
impl Visitor<'_> for SignedVisitor {
type Value = i64;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a decimal int64 string or a safe JSON integer")
}
fn visit_i64<E: DeserializeError>(self, value: i64) -> Result<Self::Value, E> {
(value.unsigned_abs() <= MAX_SAFE_INTEGER)
.then_some(value)
.ok_or_else(|| E::custom("JSON integer exceeds the safe range"))
}
fn visit_u64<E: DeserializeError>(self, value: u64) -> Result<Self::Value, E> {
i64::try_from(value)
.ok()
.filter(|value| value.unsigned_abs() <= MAX_SAFE_INTEGER)
.ok_or_else(|| E::custom("JSON integer exceeds the safe range"))
}
fn visit_str<E: DeserializeError>(self, value: &str) -> Result<Self::Value, E> {
let digits = value.strip_prefix('-').unwrap_or(value);
if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(E::custom("int64 string must contain decimal digits"));
}
value.parse().map_err(E::custom)
}
}
deserializer.deserialize_any(SignedVisitor)
}
}
pub mod u64 {
use super::*;
pub fn serialize<S: Serializer>(value: &u64, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
struct UnsignedVisitor;
impl Visitor<'_> for UnsignedVisitor {
type Value = u64;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a decimal uint64 string or a safe JSON integer")
}
fn visit_u64<E: DeserializeError>(self, value: u64) -> Result<Self::Value, E> {
(value <= MAX_SAFE_INTEGER)
.then_some(value)
.ok_or_else(|| E::custom("JSON integer exceeds the safe range"))
}
fn visit_i64<E: DeserializeError>(self, value: i64) -> Result<Self::Value, E> {
u64::try_from(value)
.map_err(E::custom)
.and_then(|value| self.visit_u64(value))
}
fn visit_str<E: DeserializeError>(self, value: &str) -> Result<Self::Value, E> {
if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(E::custom("uint64 string must contain decimal digits"));
}
value.parse().map_err(E::custom)
}
}
deserializer.deserialize_any(UnsignedVisitor)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct SafeLong(#[serde(with = "i64")] pub i64);
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct SafeUnsignedLong(#[serde(with = "u64")] pub u64);
impl From<i64> for SafeLong {
fn from(value: i64) -> Self {
Self(value)
}
}
impl From<SafeLong> for i64 {
fn from(value: SafeLong) -> Self {
value.0
}
}
impl From<u64> for SafeUnsignedLong {
fn from(value: u64) -> Self {
Self(value)
}
}
impl From<SafeUnsignedLong> for u64 {
fn from(value: SafeUnsignedLong) -> Self {
value.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SafeLongParseError;
impl Display for SafeLongParseError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("expected a decimal integer within the wire type's range")
}
}
impl Error for SafeLongParseError {}
impl Display for SafeLong {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
impl Display for SafeUnsignedLong {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
self.0.fmt(formatter)
}
}
impl FromStr for SafeLong {
type Err = SafeLongParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let digits = value.strip_prefix('-').unwrap_or(value);
if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(SafeLongParseError);
}
value.parse().map(Self).map_err(|_| SafeLongParseError)
}
}
impl FromStr for SafeUnsignedLong {
type Err = SafeLongParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(SafeLongParseError);
}
value.parse().map(Self).map_err(|_| SafeLongParseError)
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
struct Counts {
#[serde(with = "super::i64")]
signed: i64,
#[serde(with = "super::u64")]
unsigned: u64,
}
#[test]
fn integers_use_decimal_strings_and_unsafe_numbers_fail() {
assert_eq!(
serde_json::to_value(super::SafeLong(1)).unwrap(),
json!("1")
);
assert_eq!(
serde_json::to_value(super::SafeUnsignedLong(1)).unwrap(),
json!("1")
);
for invalid in ["+1", " 1", "1_000", "1", "9223372036854775808"] {
assert!(invalid.parse::<super::SafeLong>().is_err());
}
let counts = Counts {
signed: i64::MIN,
unsigned: u64::MAX,
};
let encoded = serde_json::to_value(&counts).expect("encode counts");
assert_eq!(
encoded,
json!({"signed":"-9223372036854775808","unsigned":"18446744073709551615"})
);
assert_eq!(
serde_json::from_value::<Counts>(encoded).expect("decode counts"),
counts
);
assert!(
serde_json::from_value::<Counts>(json!({"signed":9007199254740992_i64,"unsigned":1}))
.is_err()
);
assert!(
serde_json::from_value::<Counts>(json!({"signed":1,"unsigned":9007199254740992_u64}))
.is_err()
);
}
}