starknet_api/
serde_utils.rs

1//! Utilities for serialising/deserialising hexadecimal values.
2#[cfg(test)]
3#[path = "serde_utils_test.rs"]
4mod serde_utils_test;
5
6use serde::de::{Deserialize, Visitor};
7use serde::ser::{Serialize, SerializeTuple};
8use serde::Deserializer;
9
10use crate::deprecated_contract_class::ContractClassAbiEntry;
11
12/// A [BytesAsHex](`crate::serde_utils::BytesAsHex`) prefixed with '0x'.
13pub type PrefixedBytesAsHex<const N: usize> = BytesAsHex<N, true>;
14
15/// A byte array that serializes as a hex string.
16///
17/// The `PREFIXED` generic type symbolize whether a string representation of the hex value should be
18/// prefixed by `0x` or not.
19#[derive(Debug, Eq, PartialEq)]
20pub struct BytesAsHex<const N: usize, const PREFIXED: bool>(pub(crate) [u8; N]);
21
22impl<'de, const N: usize, const PREFIXED: bool> Deserialize<'de> for BytesAsHex<N, PREFIXED> {
23    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
24    where
25        D: serde::Deserializer<'de>,
26    {
27        struct ByteArrayVisitor<const N: usize, const PREFIXED: bool>;
28        impl<'de, const N: usize, const PREFIXED: bool> Visitor<'de> for ByteArrayVisitor<N, PREFIXED> {
29            type Value = BytesAsHex<N, PREFIXED>;
30
31            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32                formatter.write_str("a byte array")
33            }
34
35            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
36            where
37                A: serde::de::SeqAccess<'de>,
38            {
39                let mut res = [0u8; N];
40                let mut i = 0;
41                while let Some(value) = seq.next_element()? {
42                    res[i] = value;
43                    i += 1;
44                }
45                Ok(BytesAsHex(res))
46            }
47        }
48
49        if deserializer.is_human_readable() {
50            let s = String::deserialize(deserializer)?;
51            bytes_from_hex_str::<N, PREFIXED>(s.as_str())
52                .map_err(serde::de::Error::custom)
53                .map(BytesAsHex)
54        } else {
55            deserializer.deserialize_tuple(N, ByteArrayVisitor)
56        }
57    }
58}
59
60impl<const N: usize, const PREFIXED: bool> Serialize for BytesAsHex<N, PREFIXED> {
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: serde::Serializer,
64    {
65        if serializer.is_human_readable() {
66            let hex_str = hex_str_from_bytes::<N, PREFIXED>(self.0);
67            serializer.serialize_str(&hex_str)
68        } else {
69            let mut seq = serializer.serialize_tuple(N)?;
70            for element in &self.0[..] {
71                seq.serialize_element(element)?;
72            }
73            seq.end()
74        }
75    }
76}
77
78/// The error type returned by the inner deserialization.
79// If you need `eq`, add `impl Eq fro InnerDeserializationError {}` and read warning below.
80//
81// For some reason `hex` (now unmaintained for > 3 years) didn't implement `Eq`, even though
82// there's no reason not too, so we can't use `derive(Eq)` unfortunately.
83// Note that adding the impl is risky, because if at some point `hex` decide to add non-Eq
84// things to the error, then combined with the manual `impl Eq` this will create very nasty bugs.
85// So, for prudence, we'll hold off on adding `Eq` until we have a good reason to.
86// Existing (ignored) issue on this: https://github.com/KokaKiwi/rust-hex/issues/76.
87#[derive(thiserror::Error, Clone, Debug, PartialEq)]
88pub enum InnerDeserializationError {
89    /// Error parsing the hex string.
90    #[error(transparent)]
91    FromHex(#[from] hex::FromHexError),
92    /// Missing 0x prefix in the hex string.
93    #[error("Missing prefix 0x in {hex_str}")]
94    MissingPrefix { hex_str: String },
95    /// Unexpected input byte count.
96    #[error("Bad input - expected #bytes: {expected_byte_count}, string found: {string_found}.")]
97    BadInput { expected_byte_count: usize, string_found: String },
98}
99
100/// Deserializes a Hex decoded as string to a byte array.
101pub fn bytes_from_hex_str<const N: usize, const PREFIXED: bool>(
102    hex_str: &str,
103) -> Result<[u8; N], InnerDeserializationError> {
104    let hex_str = if PREFIXED {
105        hex_str
106            .strip_prefix("0x")
107            .ok_or(InnerDeserializationError::MissingPrefix { hex_str: hex_str.into() })?
108    } else {
109        hex_str
110    };
111
112    // Make sure string is not too long.
113    if hex_str.len() > 2 * N {
114        let mut err_str = "0x".to_owned();
115        err_str.push_str(hex_str);
116        return Err(InnerDeserializationError::BadInput {
117            expected_byte_count: N,
118            string_found: err_str,
119        });
120    }
121
122    // Pad if needed.
123    let to_add = 2 * N - hex_str.len();
124    let padded_str = vec!["0"; to_add].join("") + hex_str;
125
126    Ok(hex::decode(padded_str)?.try_into().expect("Unexpected length of deserialized hex bytes."))
127}
128
129/// Encodes a byte array to a string.
130pub fn hex_str_from_bytes<const N: usize, const PREFIXED: bool>(bytes: [u8; N]) -> String {
131    let hex_str = hex::encode(bytes);
132    let mut hex_str = hex_str.trim_start_matches('0');
133    hex_str = if hex_str.is_empty() { "0" } else { hex_str };
134    if PREFIXED { format!("0x{hex_str}") } else { hex_str.to_string() }
135}
136
137pub fn deserialize_optional_contract_class_abi_entry_vector<'de, D>(
138    deserializer: D,
139) -> Result<Option<Vec<ContractClassAbiEntry>>, D::Error>
140where
141    D: Deserializer<'de>,
142{
143    // Deserialize the field as an `Option<Vec<ContractClassAbiEntry>>`
144    let result: Result<Option<Vec<ContractClassAbiEntry>>, _> = Option::deserialize(deserializer);
145
146    // If the field contains junk or an invalid value, return `None`.
147    match result {
148        Ok(value) => Ok(value),
149        Err(_) => Ok(None),
150    }
151}