use alloy_primitives::U256;
use nautilus_core::{UnixNanos, datetime::NANOSECONDS_IN_SECOND};
use serde::{Deserialize, Deserializer};
pub fn from_str_hex_to_u64(hex_string: &str) -> Result<u64, std::num::ParseIntError> {
let without_prefix = if hex_string.starts_with("0x") || hex_string.starts_with("0X") {
&hex_string[2..]
} else {
hex_string
};
if without_prefix.len() > 16 {
return Err(u64::from_str_radix("ffffffffffffffffffffffff", 16).unwrap_err());
}
u64::from_str_radix(without_prefix, 16)
}
pub fn deserialize_hex_number<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
let hex_string = String::deserialize(deserializer)?;
from_str_hex_to_u64(hex_string.as_str()).map_err(serde::de::Error::custom)
}
pub fn deserialize_opt_hex_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
match opt {
None => Ok(None),
Some(hex_string) => from_str_hex_to_u64(hex_string.as_str())
.map(Some)
.map_err(serde::de::Error::custom),
}
}
pub fn deserialize_opt_hex_u256<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
match opt {
None => Ok(None),
Some(hex_string) => {
let without_prefix = if hex_string.starts_with("0x") || hex_string.starts_with("0X") {
&hex_string[2..]
} else {
hex_string.as_str()
};
U256::from_str_radix(without_prefix, 16)
.map(Some)
.map_err(serde::de::Error::custom)
}
}
}
pub fn deserialize_hex_timestamp<'de, D>(deserializer: D) -> Result<UnixNanos, D::Error>
where
D: Deserializer<'de>,
{
let hex_string = String::deserialize(deserializer)?;
let seconds = from_str_hex_to_u64(hex_string.as_str()).map_err(serde::de::Error::custom)?;
seconds
.checked_mul(NANOSECONDS_IN_SECOND)
.map(UnixNanos::new)
.ok_or_else(|| serde::de::Error::custom("UnixNanos overflow when converting timestamp"))
}
#[cfg(test)]
mod tests {
use alloy_primitives::U256;
use rstest::rstest;
use super::*;
#[rstest]
fn test_from_str_hex_to_u64_valid() {
assert_eq!(from_str_hex_to_u64("0x0").unwrap(), 0);
assert_eq!(from_str_hex_to_u64("0x1").unwrap(), 1);
assert_eq!(from_str_hex_to_u64("0XfF").unwrap(), 255);
assert_eq!(from_str_hex_to_u64("0xff").unwrap(), 255);
assert_eq!(from_str_hex_to_u64("0xffffffffffffffff").unwrap(), u64::MAX);
assert_eq!(from_str_hex_to_u64("1234abcd").unwrap(), 0x1234abcd);
}
#[rstest]
fn test_from_str_hex_to_u64_too_long() {
let too_long = "0x1ffffffffffffffff";
assert!(from_str_hex_to_u64(too_long).is_err());
let very_long = "0x123456789abcdef123456789abcdef";
assert!(from_str_hex_to_u64(very_long).is_err());
}
#[rstest]
fn test_from_str_hex_to_u64_invalid_chars() {
assert!(from_str_hex_to_u64("0xzz").is_err());
assert!(from_str_hex_to_u64("0x123g").is_err());
}
#[rstest]
fn test_deserialize_hex_timestamp() {
let timestamp_hex = "0x64b5f3bb"; let expected_nanos = 0x64b5f3bb * NANOSECONDS_IN_SECOND;
assert_eq!(
from_str_hex_to_u64(timestamp_hex).unwrap() * NANOSECONDS_IN_SECOND,
expected_nanos
);
}
#[rstest]
fn test_deserialize_opt_hex_u256_present() {
let json = "\"0x1a\"";
let value: Option<U256> = serde_json::from_str(json).unwrap();
assert_eq!(value, Some(U256::from(26u8)));
}
#[rstest]
fn test_deserialize_opt_hex_u256_null() {
let json = "null";
let value: Option<U256> = serde_json::from_str(json).unwrap();
assert!(value.is_none());
}
}