Skip to content

Commit

Permalink
feat: accept numbers for NUM_AS_HEX (#491)
Browse files Browse the repository at this point in the history
While the official specs indicate that `NUM_AS_HEX` must be strings,
some Starknet libraries expect numbers to be acceptable in certain
cases. Adding support for this technically breaks the specs but it's
acceptable since it's only on the deserialization side.

Also changes the `NumAsHex` implementation to remove the unnecessary
allocation for `String`. This makes deserialization slightly more
efficient.
  • Loading branch information
xJonathanLEI authored Nov 1, 2023
1 parent 1d2eb07 commit e8e9f6c
Showing 1 changed file with 59 additions and 5 deletions.
64 changes: 59 additions & 5 deletions starknet-core/src/types/serde_impls.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use alloc::{format, string::String};
use alloc::{fmt::Formatter, format};

use serde::{Deserialize, Deserializer, Serialize};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
use serde_with::{DeserializeAs, SerializeAs};

use super::{SyncStatus, SyncStatusType};

pub(crate) struct NumAsHex;

struct NumAsHexVisitor;

impl SerializeAs<u64> for NumAsHex {
fn serialize_as<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
Expand All @@ -21,14 +23,48 @@ impl<'de> DeserializeAs<'de, u64> for NumAsHex {
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
match u64::from_str_radix(&value[2..], 16) {
deserializer.deserialize_any(NumAsHexVisitor)
}
}

impl<'de> Visitor<'de> for NumAsHexVisitor {
type Value = u64;

fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result {
write!(formatter, "string or number")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match u64::from_str_radix(v.trim_start_matches("0x"), 16) {
Ok(value) => Ok(value),
Err(err) => Err(serde::de::Error::custom(format!(
"invalid hex string: {err}"
))),
}
}

fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match v.try_into() {
Ok(value) => self.visit_u64(value),
Err(_) => Err(serde::de::Error::custom(format!(
"value cannot be negative: {}",
v
))),
}
}

fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v)
}
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -231,7 +267,13 @@ mod enum_ser_impls {

#[cfg(test)]
mod tests {
use super::super::{BlockId, BlockTag, FieldElement};
use serde::Deserialize;
use serde_with::serde_as;

use super::{
super::{BlockId, BlockTag, FieldElement},
NumAsHex,
};

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
Expand All @@ -251,4 +293,16 @@ mod tests {
assert_eq!(serde_json::from_str::<BlockId>(json).unwrap(), block_id);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_num_as_hex_deser() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Deserialize)]
struct Value(#[serde_as(as = "NumAsHex")] u64);

for (num, json) in [(Value(100), "\"0x64\""), (Value(100), "100")].into_iter() {
assert_eq!(serde_json::from_str::<Value>(json).unwrap(), num);
}
}
}

0 comments on commit e8e9f6c

Please sign in to comment.