scale_value/string_impls/custom_parsers/
hex.rs1use crate::prelude::*;
17use crate::{
18 stringify::{ParseError, ParseErrorKind},
19 Value,
20};
21
22pub fn parse_hex(s: &mut &str) -> Option<Result<Value<()>, ParseError>> {
31 if !s.starts_with("0x") {
32 return None;
33 }
34
35 let bytes = s.as_bytes();
36 let mut composite_values = vec![];
37
38 let mut idx = 2;
40 let mut last_nibble = None;
41 loop {
42 let Some(b) = bytes.get(idx) else {
44 break;
45 };
46
47 if !b.is_ascii_alphanumeric() {
49 break;
50 }
51
52 let hex_nibble = match *b {
54 b'A'..=b'F' => b - b'A' + 10,
55 b'a'..=b'f' => b - b'a' + 10,
56 b'0'..=b'9' => b - b'0',
57 b => {
58 return Some(Err(
59 ParseErrorKind::custom(ParseHexError::InvalidChar(b as char)).at(idx)
60 ))
61 }
62 };
63
64 match last_nibble {
65 None => {
66 last_nibble = Some(hex_nibble)
68 }
69 Some(n) => {
70 let byte = n * 16 + hex_nibble;
72 composite_values.push(Value::u128(byte as u128));
73 last_nibble = None;
74 }
75 }
76
77 idx += 1;
78 }
79
80 if last_nibble.is_some() {
82 return Some(Err(ParseErrorKind::custom(ParseHexError::WrongLength).between(0, idx)));
83 }
84
85 *s = unsafe { core::str::from_utf8_unchecked(&bytes[idx..]) };
92 Some(Ok(Value::unnamed_composite(composite_values)))
93}
94
95#[derive(Debug, PartialEq, Clone, thiserror::Error)]
96#[allow(missing_docs)]
97pub enum ParseHexError {
98 #[error("Invalid hex character: {0}")]
99 InvalidChar(char),
100 #[error("Hex string is the wrong length; should be an even length")]
101 WrongLength,
102}
103
104#[cfg(test)]
105mod test {
106 use super::*;
107
108 #[test]
109 fn parses_same_as_hex_crate() {
110 let expects = ["0x", "0x00", "0x000102030405060708090A0B", "0xDEADBEEF", "0x00BAB10C"];
111
112 for input in expects {
113 let expected_hex = hex::decode(input.trim_start_matches("0x")).expect("valid hex");
114 let cursor = &mut &*input;
115 let hex = parse_hex(cursor).expect("valid hex expected").expect("no error expected");
116
117 assert_eq!(hex, Value::from_bytes(expected_hex), "values should match");
118 }
119 }
120
121 #[test]
122 fn consumes_parsed_hex() {
123 let expects =
124 [("0x foo", " foo"), ("0x00,bar", ",bar"), ("0x123456-2", "-2"), ("0xDEADBEEF ", " ")];
125
126 for (input, expected_remaining) in expects {
127 let cursor = &mut &*input;
128 let _ = parse_hex(cursor).expect("valid hex expected").expect("no error expected");
129
130 assert_eq!(*cursor, expected_remaining);
131 }
132 }
133
134 #[test]
135 fn err_wrong_length() {
136 let expects = ["0x1", "0x123"];
137
138 for input in expects {
139 let cursor = &mut &*input;
140 let err =
141 parse_hex(cursor).expect("some result expected").expect_err("an error is expected");
142
143 assert_eq!(err.start_loc, 0);
144 assert_eq!(err.end_loc, Some(input.len()));
145
146 let ParseErrorKind::Custom(err) = err.err else { panic!("expected custom error") };
147
148 assert_eq!(err, ParseHexError::WrongLength.to_string());
149 assert_eq!(input, *cursor);
150 }
151 }
152
153 #[test]
154 fn err_invalid_char() {
155 let expects = [("0x12345x", 'x', 7), ("0x123h4", 'h', 5), ("0xG23h4", 'G', 2)];
156
157 for (input, bad_char, pos) in expects {
158 let cursor = &mut &*input;
159 let err =
160 parse_hex(cursor).expect("some result expected").expect_err("an error is expected");
161
162 assert_eq!(err.start_loc, pos);
163 assert!(err.end_loc.is_none());
164
165 let ParseErrorKind::Custom(err) = err.err else { panic!("expected custom error") };
166
167 assert_eq!(err, ParseHexError::InvalidChar(bad_char).to_string());
168 assert_eq!(input, *cursor);
169 }
170 }
171
172 #[test]
173 fn empty_string_doesnt_panic() {
174 assert!(parse_hex(&mut "").is_none());
175 assert!(parse_hex(&mut "0").is_none());
176 }
177}