serde_encoded_bytes/encoding/
hex.rs

1use alloc::{format, string::String, vec::Vec};
2
3use serde::de;
4
5use super::Encoding;
6
7/// Encodes the byte sequence into a `0x`-prefixed hexadecimal representation.
8pub struct Hex;
9
10impl Encoding for Hex {
11    fn encode(bytes: &[u8]) -> String {
12        format!("0x{}", hex::encode(bytes))
13    }
14
15    fn decode<E: de::Error>(string: &str) -> Result<Vec<u8>, E> {
16        let digits = string.strip_prefix("0x").ok_or_else(|| {
17            de::Error::invalid_value(
18                de::Unexpected::Str(string),
19                &"0x-prefixed hex-encoded bytes",
20            )
21        })?;
22        hex::decode(digits).map_err(de::Error::custom)
23    }
24}
25
26#[cfg(test)]
27mod tests {
28    use alloc::string::{String, ToString};
29
30    use serde::{Deserialize, Serialize};
31
32    use super::Hex;
33    use crate::ArrayLike;
34
35    #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
36    struct ArrayStruct(#[serde(with = "ArrayLike::<Hex>")] [u8; 4]);
37
38    fn hr_serialize<T: Serialize>(value: T) -> Result<String, String> {
39        serde_json::to_string(&value).map_err(|err| err.to_string())
40    }
41
42    fn hr_deserialize<'de, T: Deserialize<'de>>(string: &'de str) -> Result<T, String> {
43        serde_json::from_str::<T>(string).map_err(|err| err.to_string())
44    }
45
46    #[test]
47    fn roundtrip() {
48        let val = ArrayStruct([1, 0xf2, 3, 0xf4]);
49
50        let val_str = hr_serialize(&val).unwrap();
51        assert_eq!(val_str, "\"0x01f203f4\"");
52        let val_back = hr_deserialize::<ArrayStruct>(&val_str).unwrap();
53        assert_eq!(val, val_back);
54    }
55
56    #[test]
57    fn errors() {
58        assert_eq!(
59            hr_deserialize::<ArrayStruct>("\"01f203f4\"").unwrap_err(),
60            concat![
61                "invalid value: string \"01f203f4\", expected 0x-prefixed ",
62                "hex-encoded bytes at line 1 column 10"
63            ]
64        );
65        assert_eq!(
66            hr_deserialize::<ArrayStruct>("\"0\"").unwrap_err(),
67            "invalid value: string \"0\", expected 0x-prefixed hex-encoded bytes at line 1 column 3"
68        );
69    }
70
71    #[test]
72    fn multi_byte_character() {
73        // A regression test for a bug in validating a possible hex string
74        // that could lead to a panic.
75        // `str::get()` takes an index in bytes, but requires it to be aligned
76        // to the end of a character, so it can fail for multi-byte characters
77        // even if the index is technically lower than `str::len()`.
78
79        // This is a unicode "AE" with a 3-byte UTF-8 encoding (0xE1 0xB4 0x81)
80        assert_eq!(
81            hr_deserialize::<ArrayStruct>("\"\u{1D01}\"").unwrap_err(),
82            concat![
83                "invalid value: string \"\u{1D01}\", expected 0x-prefixed ",
84                "hex-encoded bytes at line 1 column 5"
85            ]
86        );
87    }
88}