use ckb_types::{
core, packed,
prelude::{Pack, Unpack},
};
use serde::{
de::{Error, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{fmt, marker, num};
pub trait Uint: Copy + fmt::LowerHex {
const NAME: &'static str;
fn from_str_radix(src: &str, radix: u32) -> Result<Self, num::ParseIntError>;
}
#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Ord, Eq, Hash, Debug)]
pub struct JsonUint<T: Uint>(pub(crate) T);
struct JsonUintVisitor<T: Uint>(marker::PhantomData<T>);
impl<T: Uint> JsonUint<T> {
pub fn value(self) -> T {
self.0
}
}
impl<T: Uint> fmt::Display for JsonUint<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "0x{:x}", self.value())
}
}
impl<T: Uint> From<T> for JsonUint<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: Uint> Serialize for JsonUint<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<T: Uint> JsonUintVisitor<T> {
#[inline]
fn expecting(formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a hex-encoded, 0x-prefixed {}", T::NAME)
}
#[inline]
fn visit_str<E>(value: &str) -> Result<JsonUint<T>, E>
where
E: Error,
{
let value_bytes = value.as_bytes();
if value_bytes.len() < 3 || &value_bytes[..2] != b"0x" {
return Err(Error::custom(format!(
"Invalid {} {}: without `0x` prefix",
T::NAME,
value
)));
}
if value_bytes[2] == b'0' && value_bytes.len() > 3 {
return Err(Error::custom(format!(
"Invalid {} {}: with redundant leading zeros",
T::NAME,
value,
)));
}
T::from_str_radix(&value[2..], 16)
.map(JsonUint)
.map_err(|e| Error::custom(format!("Invalid {} {}: {}", T::NAME, value, e)))
}
}
macro_rules! def_json_uint {
($alias:ident, $inner:ident, $bits:expr) => {
#[doc = "The "]
#[doc = $bits]
#[doc = r#" unsigned integer type encoded as the 0x-prefixed hex string in JSON.
## Examples
| JSON | Decimal Value |
| -------| ---------------------------- |
| "0x0" | 0 |
| "0x10" | 16 |
| "10" | Invalid, 0x is required |
| "0x01" | Invalid, redundant leading 0 |"#]
pub type $alias = JsonUint<$inner>;
impl Uint for $inner {
const NAME: &'static str = stringify!($alias);
fn from_str_radix(src: &str, radix: u32) -> Result<Self, num::ParseIntError> {
$inner::from_str_radix(src, radix)
}
}
impl From<JsonUint<$inner>> for $inner {
fn from(value: JsonUint<$inner>) -> Self {
value.value()
}
}
};
}
macro_rules! impl_serde_deserialize {
($visitor:ident, $inner:ident) => {
impl<'a> Deserialize<'a> for JsonUint<$inner> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_str($visitor)
}
}
struct $visitor;
impl<'a> Visitor<'a> for $visitor {
type Value = JsonUint<$inner>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
JsonUintVisitor::<$inner>::expecting(formatter)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
JsonUintVisitor::<$inner>::visit_str(value)
}
}
};
}
macro_rules! impl_pack_and_unpack {
($packed:ident, $inner:ident) => {
impl Pack<packed::$packed> for JsonUint<$inner> {
fn pack(&self) -> packed::$packed {
self.value().pack()
}
}
impl Unpack<JsonUint<$inner>> for packed::$packed {
fn unpack(&self) -> JsonUint<$inner> {
Unpack::<$inner>::unpack(self).into()
}
}
};
}
def_json_uint!(Uint32, u32, "32-bit");
def_json_uint!(Uint64, u64, "64-bit");
def_json_uint!(Uint128, u128, "128-bit");
impl_serde_deserialize!(Uint32Visitor, u32);
impl_serde_deserialize!(Uint64Visitor, u64);
impl_serde_deserialize!(Uint128Visitor, u128);
impl_pack_and_unpack!(Uint32, u32);
impl_pack_and_unpack!(Uint64, u64);
impl_pack_and_unpack!(Uint128, u128);
impl From<core::Capacity> for JsonUint<u64> {
fn from(value: core::Capacity) -> Self {
JsonUint(value.as_u64())
}
}
impl From<core::EpochNumberWithFraction> for JsonUint<u64> {
fn from(value: core::EpochNumberWithFraction) -> Self {
JsonUint(value.full_value())
}
}
impl From<JsonUint<u64>> for core::Capacity {
fn from(value: JsonUint<u64>) -> Self {
core::Capacity::shannons(value.value())
}
}
#[cfg(tests)]
mod tests {
macro_rules! test_json_uint {
($tests_name:ident, $name:ident, $inner:ident) => {
mod $tests_name {
use crate::$name;
#[test]
fn serialize() {
assert_eq!(r#""0xd""#, serde_json::to_string(&$name::from(13)).unwrap());
assert_eq!(r#""0x0""#, serde_json::to_string(&$name::from(0)).unwrap());
}
#[test]
fn deserialize_hexadecimal() {
let s = r#""0xa""#;
let deserialized: $name = serde_json::from_str(s).unwrap();
assert_eq!(deserialized, $name::from(10));
}
#[test]
fn deserialize_decimal() {
let s = r#""10""#;
let ret: Result<$name, _> = serde_json::from_str(s);
assert!(ret.is_err(), ret);
}
#[test]
fn deserialize_with_redundant_leading_zeros() {
let cases = vec![r#""0x01""#, r#""0x00""#];
for s in cases {
let ret: Result<$name, _> = serde_json::from_str(s);
assert!(ret.is_err(), ret);
let err = ret.unwrap_err();
assert!(
err.to_string().contains("with redundant leading zeros"),
err,
);
}
}
fn deserialize_with_uppercase() {
let cases = vec![r#""0xFF""#, r#""0xfF""#];
for s in cases {
let ret: Result<$name, _> = serde_json::from_str(s);
assert!(ret.is_ok());
}
}
#[test]
fn deserialize_too_large() {
let s = format!(r#""0x{:x}0""#, $inner::max_value());
let ret: Result<$name, _> = serde_json::from_str(&s);
assert!(ret.is_err(), ret);
let err = ret.unwrap_err();
assert!(
err.to_string()
.contains("number too large to fit in target type"),
err,
);
}
}
};
}
test_json_uint!(uint32, Uint32, u32);
test_json_uint!(uint64, Uint64, u64);
test_json_uint!(uint128, Uint128, u128);
}