use core::marker::PhantomData;
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "alloc")]
use alloc::string::ToString;
use crate::core_type::D38;
impl<const SCALE: u32> Serialize for D38<SCALE> {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
#[cfg(feature = "alloc")]
{
serializer.serialize_str(&self.0.to_string())
}
#[cfg(not(feature = "alloc"))]
{
let _ = serializer;
Err(serde::ser::Error::custom(
"decimal-scaled: human-readable serialisation requires the `alloc` feature",
))
}
} else {
serializer.serialize_bytes(&self.0.to_le_bytes())
}
}
}
impl<'de, const SCALE: u32> Deserialize<'de> for D38<SCALE> {
#[inline]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let visitor = decimal_serde::DecimalVisitor::<SCALE>(PhantomData);
if deserializer.is_human_readable() {
deserializer.deserialize_any(visitor)
} else {
deserializer.deserialize_bytes(visitor)
}
}
}
pub mod decimal_serde {
use super::{Serializer, D38, Serialize, Deserializer, Deserialize, PhantomData, Visitor};
#[inline]
pub fn serialize<const SCALE: u32, S: Serializer>(
v: &D38<SCALE>,
s: S,
) -> Result<S::Ok, S::Error> {
v.serialize(s)
}
#[inline]
pub fn deserialize<'de, const SCALE: u32, D: Deserializer<'de>>(
d: D,
) -> Result<D38<SCALE>, D::Error> {
D38::<SCALE>::deserialize(d)
}
pub struct DecimalVisitor<const SCALE: u32>(pub PhantomData<()>);
impl<'de, const SCALE: u32> Visitor<'de> for DecimalVisitor<SCALE> {
type Value = D38<SCALE>;
fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(
"a base-10 i128 integer string, 16 little-endian bytes, \
or a native integer",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
let bytes = v.as_bytes();
if bytes.is_empty() {
return Err(serde::de::Error::custom(
"decimal-scaled: empty string is not a valid i128 wire",
));
}
if bytes[0] == b'+' {
return Err(serde::de::Error::custom(
"decimal-scaled: leading `+` is not part of the canonical wire format",
));
}
v.parse::<i128>()
.map(D38::<SCALE>::from_bits)
.map_err(|_| {
serde::de::Error::custom(
"decimal-scaled: expected a base-10 i128 integer string",
)
})
}
fn visit_borrowed_str<E: serde::de::Error>(self, v: &'de str) -> Result<Self::Value, E> {
self.visit_str(v)
}
#[cfg(feature = "alloc")]
fn visit_string<E: serde::de::Error>(
self,
v: alloc::string::String,
) -> Result<Self::Value, E> {
self.visit_str(&v)
}
fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
let arr: [u8; 16] = v.try_into().map_err(|_| {
serde::de::Error::invalid_length(
v.len(),
&"exactly 16 little-endian bytes for an i128",
)
})?;
Ok(D38::<SCALE>::from_bits(i128::from_le_bytes(arr)))
}
fn visit_borrowed_bytes<E: serde::de::Error>(
self,
v: &'de [u8],
) -> Result<Self::Value, E> {
self.visit_bytes(v)
}
#[cfg(feature = "alloc")]
fn visit_byte_buf<E: serde::de::Error>(
self,
v: alloc::vec::Vec<u8>,
) -> Result<Self::Value, E> {
self.visit_bytes(&v)
}
fn visit_i8<E: serde::de::Error>(self, v: i8) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_i16<E: serde::de::Error>(self, v: i16) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_i32<E: serde::de::Error>(self, v: i32) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_i128<E: serde::de::Error>(self, v: i128) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(v))
}
fn visit_u8<E: serde::de::Error>(self, v: u8) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_u16<E: serde::de::Error>(self, v: u16) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_u32<E: serde::de::Error>(self, v: u32) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
Ok(D38::<SCALE>::from_bits(i128::from(v)))
}
fn visit_u128<E: serde::de::Error>(self, v: u128) -> Result<Self::Value, E> {
i128::try_from(v).map(D38::<SCALE>::from_bits).map_err(|_| {
serde::de::Error::custom(
"decimal-scaled: u128 value exceeds i128 storage range",
)
})
}
}
}
#[cfg(all(test, feature = "alloc", feature = "serde"))]
mod tests {
use super::*;
use crate::core_type::{D38, D38s12};
use serde::de::value::{Error as DeError, StrDeserializer};
use serde::de::IntoDeserializer;
use alloc::format;
#[test]
fn deserialize_canonical_zero_string() {
let de: StrDeserializer<DeError> = "0".into_deserializer();
let v: D38s12 = D38s12::deserialize(de).unwrap();
assert_eq!(v, D38s12::ZERO);
}
#[test]
fn visitor_accepts_scaled_one_str() {
let visitor = decimal_serde::DecimalVisitor::<12>(PhantomData);
let v: D38s12 =
<_ as Visitor>::visit_str::<DeError>(visitor, "1000000000000").unwrap();
assert_eq!(v, D38s12::ONE);
}
#[test]
fn visitor_rejects_decimal_point_str() {
let visitor = decimal_serde::DecimalVisitor::<12>(PhantomData);
let res: Result<D38s12, _> =
<_ as Visitor>::visit_str::<DeError>(visitor, "1.5");
assert!(res.is_err(), "expected reject; got Ok({:?})", res);
}
#[test]
fn visitor_accepts_i64_as_storage() {
let visitor = decimal_serde::DecimalVisitor::<12>(PhantomData);
let v: D38s12 = <_ as Visitor>::visit_i64::<DeError>(visitor, -5).unwrap();
assert_eq!(v.to_bits(), -5);
}
#[test]
fn visitor_accepts_u64_max() {
let visitor = decimal_serde::DecimalVisitor::<12>(PhantomData);
let v: D38s12 =
<_ as Visitor>::visit_u64::<DeError>(visitor, u64::MAX).unwrap();
assert_eq!(v.to_bits(), u64::MAX as i128);
}
#[test]
fn visitor_rejects_u128_above_i128_max() {
let visitor = decimal_serde::DecimalVisitor::<12>(PhantomData);
let res: Result<D38s12, _> = <_ as Visitor>::visit_u128::<DeError>(
visitor,
(i128::MAX as u128) + 1,
);
assert!(res.is_err(), "expected overflow reject; got Ok({:?})", res);
}
#[test]
fn json_one_serialises_as_scaled_integer_string() {
let json = serde_json::to_string(&D38s12::ONE).unwrap();
assert_eq!(json, "\"1000000000000\"");
}
#[test]
fn json_zero_serialises_as_zero_string() {
let json = serde_json::to_string(&D38s12::ZERO).unwrap();
assert_eq!(json, "\"0\"");
}
#[test]
fn json_one_round_trips() {
let json = serde_json::to_string(&D38s12::ONE).unwrap();
let back: D38s12 = serde_json::from_str(&json).unwrap();
assert_eq!(back, D38s12::ONE);
}
#[test]
fn json_zero_round_trips() {
let json = serde_json::to_string(&D38s12::ZERO).unwrap();
let back: D38s12 = serde_json::from_str(&json).unwrap();
assert_eq!(back, D38s12::ZERO);
}
#[test]
fn json_negative_round_trips() {
let v = D38s12::from(-5_i32);
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, "\"-5000000000000\"");
let back: D38s12 = serde_json::from_str(&json).unwrap();
assert_eq!(back, v);
assert_eq!(back.to_bits(), -5_000_000_000_000_i128);
}
#[test]
fn json_max_round_trips() {
let json = serde_json::to_string(&D38s12::MAX).unwrap();
let back: D38s12 = serde_json::from_str(&json).unwrap();
assert_eq!(back, D38s12::MAX);
}
#[test]
fn json_min_round_trips() {
let json = serde_json::to_string(&D38s12::MIN).unwrap();
let back: D38s12 = serde_json::from_str(&json).unwrap();
assert_eq!(back, D38s12::MIN);
}
#[test]
fn json_string_matches_i128_to_string() {
let raw: i128 = -123_456_789_012_345_678_901_234_567_890_i128;
let v = D38s12::from_bits(raw);
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, format!("\"{}\"", raw));
}
#[test]
fn json_rejects_decimal_point_string() {
let res: Result<D38s12, _> = serde_json::from_str("\"1.5\"");
assert!(res.is_err(), "expected reject; got Ok({:?})", res);
}
#[test]
fn json_rejects_scientific_notation_string() {
let res: Result<D38s12, _> = serde_json::from_str("\"1e6\"");
assert!(res.is_err(), "expected reject; got Ok({:?})", res);
}
#[test]
fn json_rejects_not_a_number_string() {
let res: Result<D38s12, _> = serde_json::from_str("\"not-a-number\"");
assert!(res.is_err(), "expected reject; got Ok({:?})", res);
}
#[test]
fn json_rejects_empty_string() {
let res: Result<D38s12, _> = serde_json::from_str("\"\"");
assert!(res.is_err(), "expected reject; got Ok({:?})", res);
}
#[test]
fn json_rejects_leading_whitespace_string() {
let res: Result<D38s12, _> = serde_json::from_str("\" 42\"");
assert!(res.is_err(), "expected reject; got Ok({:?})", res);
}
#[test]
fn json_rejects_plus_prefix() {
let res: Result<D38s12, _> = serde_json::from_str("\"+42\"");
assert!(res.is_err(), "expected reject; got Ok({:?})", res);
}
#[test]
fn json_accepts_bare_integer_number_as_storage() {
let back: D38s12 = serde_json::from_str("42").unwrap();
assert_eq!(back.to_bits(), 42_i128);
}
#[test]
fn postcard_one_round_trips() {
let bytes: alloc::vec::Vec<u8> = postcard::to_allocvec(&D38s12::ONE).unwrap();
let raw = D38s12::ONE.to_bits().to_le_bytes();
assert!(bytes.windows(16).any(|w| w == raw));
let back: D38s12 = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back, D38s12::ONE);
}
#[test]
fn postcard_zero_round_trips() {
let bytes: alloc::vec::Vec<u8> = postcard::to_allocvec(&D38s12::ZERO).unwrap();
let back: D38s12 = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back, D38s12::ZERO);
}
#[test]
fn postcard_negative_round_trips() {
let v = D38s12::from(-5_i32);
let bytes: alloc::vec::Vec<u8> = postcard::to_allocvec(&v).unwrap();
let back: D38s12 = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back, v);
}
#[test]
fn postcard_max_round_trips() {
let bytes: alloc::vec::Vec<u8> = postcard::to_allocvec(&D38s12::MAX).unwrap();
let back: D38s12 = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back, D38s12::MAX);
}
#[test]
fn postcard_min_round_trips() {
let bytes: alloc::vec::Vec<u8> = postcard::to_allocvec(&D38s12::MIN).unwrap();
let back: D38s12 = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back, D38s12::MIN);
}
#[test]
fn postcard_byte_order_matches_le() {
let v = D38s12::from_bits(0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210_i128);
let bytes: alloc::vec::Vec<u8> = postcard::to_allocvec(&v).unwrap();
let raw = v.to_bits().to_le_bytes();
let found = bytes.windows(16).position(|w| w == raw);
assert!(found.is_some(), "expected raw LE bytes embedded; got {:?}", bytes);
assert_eq!(raw[0], 0x10); assert_eq!(raw[15], 0x01); }
#[test]
fn cross_format_json_string_matches_le_bytes() {
let v = D38s12::from(42_i32);
let json = serde_json::to_string(&v).unwrap();
let inner = json.trim_matches('"');
let parsed: i128 = inner.parse().unwrap();
let json_bytes = parsed.to_le_bytes();
let direct_bytes = v.to_bits().to_le_bytes();
assert_eq!(json_bytes, direct_bytes);
}
#[test]
fn cross_scale_wire_is_storage_only() {
let raw: i128 = 1_500_000_000_000;
let v12 = D38::<12>::from_bits(raw);
let v6 = D38::<6>::from_bits(raw);
assert_eq!(serde_json::to_string(&v12).unwrap(), "\"1500000000000\"");
assert_eq!(serde_json::to_string(&v6).unwrap(), "\"1500000000000\"");
}
#[test]
fn decimal_serde_helper_round_trips() {
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
struct Holder {
#[serde(with = "crate::serde_helpers::decimal_serde")]
length: D38<12>,
}
let h = Holder {
length: D38s12::from(7_i32),
};
let json = serde_json::to_string(&h).unwrap();
assert_eq!(json, r#"{"length":"7000000000000"}"#);
let back: Holder = serde_json::from_str(&json).unwrap();
assert_eq!(back, h);
}
}
#[cfg(any(feature = "d76", feature = "d153", feature = "d307", feature = "wide", feature = "x-wide"))]
macro_rules! decl_wide_serde {
($Type:ident, $Storage:ty, $bytes_len:literal) => {
impl<const SCALE: u32> Serialize for $crate::core_type::$Type<SCALE> {
#[inline]
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
#[cfg(feature = "alloc")]
{
s.serialize_str(&self.0.to_string())
}
#[cfg(not(feature = "alloc"))]
{
let _ = s;
Err(serde::ser::Error::custom(
"decimal-scaled: human-readable serialisation requires `alloc`",
))
}
} else {
let mut bytes = [0u8; $bytes_len];
let limbs = self.0.limbs_le();
for (i, limb) in limbs.iter().enumerate() {
bytes[i * 16..(i + 1) * 16].copy_from_slice(&limb.to_le_bytes());
}
s.serialize_bytes(&bytes)
}
}
}
impl<'de, const SCALE: u32> Deserialize<'de> for $crate::core_type::$Type<SCALE> {
#[inline]
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V<const S: u32>;
impl<'de, const S: u32> Visitor<'de> for V<S> {
type Value = $crate::core_type::$Type<S>;
fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(concat!(
"a base-10 integer string or ",
stringify!($bytes_len),
" little-endian bytes for ",
stringify!($Type),
))
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
let parsed = <$Storage>::from_str_radix(v, 10).map_err(|_| {
serde::de::Error::custom(concat!(
stringify!($Type),
": invalid base-10 integer string",
))
})?;
Ok(<$crate::core_type::$Type<S>>::from_bits(parsed))
}
fn visit_borrowed_str<E: serde::de::Error>(self, v: &'de str) -> Result<Self::Value, E> {
self.visit_str(v)
}
#[cfg(feature = "alloc")]
fn visit_string<E: serde::de::Error>(self, v: alloc::string::String) -> Result<Self::Value, E> {
self.visit_str(&v)
}
fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
if v.len() != $bytes_len {
return Err(serde::de::Error::invalid_length($bytes_len, &self));
}
let mut limbs = [0u128; $bytes_len / 16];
for (i, limb) in limbs.iter_mut().enumerate() {
let mut buf = [0u8; 16];
buf.copy_from_slice(&v[i * 16..(i + 1) * 16]);
*limb = u128::from_le_bytes(buf);
}
Ok(<$crate::core_type::$Type<S>>::from_bits(<$Storage>::from_limbs_le(limbs)))
}
fn visit_borrowed_bytes<E: serde::de::Error>(self, v: &'de [u8]) -> Result<Self::Value, E> {
self.visit_bytes(v)
}
}
if d.is_human_readable() {
d.deserialize_str(V::<SCALE>)
} else {
d.deserialize_bytes(V::<SCALE>)
}
}
}
};
}
#[cfg(any(feature = "d76", feature = "wide"))]
decl_wide_serde!(D76, crate::wide_int::Int256, 32);
#[cfg(any(feature = "d153", feature = "wide"))]
decl_wide_serde!(D153, crate::wide_int::Int512, 64);
#[cfg(any(feature = "d307", feature = "wide"))]
decl_wide_serde!(D307, crate::wide_int::Int1024, 128);
#[cfg(all(test, feature = "wide"))]
mod wide_serde_tests {
use crate::D76;
#[test]
fn d76_human_readable_round_trip() {
let v = D76::<12>::from_int(1_234_567_i128);
let json = serde_json::to_string(&v).unwrap();
let back: D76<12> = serde_json::from_str(&json).unwrap();
assert_eq!(back, v);
}
#[test]
fn d76_negative_human_readable_round_trip() {
let v = -D76::<12>::from_int(987_654_321_i128);
let json = serde_json::to_string(&v).unwrap();
let back: D76<12> = serde_json::from_str(&json).unwrap();
assert_eq!(back, v);
}
#[test]
fn d76_binary_round_trip() {
let v = D76::<12>::from_int(42_i128);
let bytes = postcard::to_allocvec(&v).unwrap();
let back: D76<12> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back, v);
}
}