trick_data/services/convert/
from_hex.rs

1use std::string::String;
2
3#[derive(Debug, PartialEq)]
4pub enum ConvertError {
5    HexStringToBytes,
6    ToUtf8,
7}
8
9pub trait FromHex {
10    fn hex_to_string(&self) -> Result<String, ConvertError>;
11}
12
13impl FromHex for str {
14    fn hex_to_string(&self) -> Result<String, ConvertError> {
15        let mut data = String::from(self);
16
17        data = remove_delimiters(data);
18
19        let decoded_bytes = match hex::decode(data) {
20            Ok(decoded_bytes) => decoded_bytes,
21            Err(_) => return Err(ConvertError::HexStringToBytes),
22        };
23
24        return match std::str::from_utf8(&decoded_bytes) {
25            Ok(decoded_data) => Ok(decoded_data.to_string()),
26            Err(_) => return Err(ConvertError::ToUtf8),
27        }
28    }
29}
30
31fn remove_delimiters(data: String) -> String {
32    data.replace(" ", "")
33}
34
35#[cfg(test)]
36mod test {
37    use super::{FromHex, ConvertError};
38    use std::string::String;
39
40    /// Common way to test positive test cases
41    macro_rules! positive_tests {
42        ($($name:ident: $value:expr,)*) => {
43            $(
44                #[test]
45                fn $name() {
46                    // Aggregate
47                    let (input_data, expected) = $value;
48
49                    // Act
50                    let actual = input_data.hex_to_string().unwrap();
51
52                    // Assert
53                    assert_eq!(actual, expected)
54                }
55            )*
56        }
57    }
58
59    /// Negative tests
60    macro_rules! negative_tests {
61        ($($name:ident: $values:expr,)*) => {
62            $(
63                #[test]
64                fn $name() {
65                    // Aggregate
66                    let (input_data, expected) = $values;
67
68                    // Act
69                    let actual = input_data.hex_to_string().unwrap_err();
70
71                    // Assert
72                    assert_eq!(actual, expected)
73                }
74            )*
75        }
76    }
77
78    positive_tests! {
79        empty_string: (String::new(), ""),
80        digits: ("30 31 32 33 34 35 36 37 38 39", "0123456789"),
81        lowercase_ascii: (
82            "61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a",
83            "abcdefghijklmnopqrstuvwxyz"),
84        uppercase_ascii: (
85            "41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a",
86            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
87        utf8_1byte_first_code_point: ("00", "\u{0000}"),
88        utf8_1byte_last_code_point: ("7f", "\u{007F}"),
89        utf8_2bytes_first_code_point: ("c2 80", "\u{0080}"),
90        utf8_2bytes_last_code_point: ("df bf", "\u{07FF}"),
91        utf8_3bytes_first_code_point: ("e0 a0 80", "\u{0800}"),
92        utf8_3bytes_last_code_point: ("EF BF BF", "\u{FFFF}"),
93        utf8_4bytes_first_code_point: ("F0 90 80 80", "\u{10000}"),
94        utf8_4bytes_last_code_point: ("F4 8F BF BF", "\u{10FFFF}"),
95    }
96
97    negative_tests!{
98        utf8_neg_1byte_out_of_range: ("80", ConvertError::ToUtf8),
99        utf8_neg_2bytes_out_of_range_to_low: ("c2 7F", ConvertError::ToUtf8),
100        utf8_neg_2bytes_out_of_range_to_high: ("df c0", ConvertError::ToUtf8),
101        utf8_neg_3bytes_out_of_range_to_low: ("e0 a0 7F", ConvertError::ToUtf8),
102        utf8_neg_3bytes_out_of_range_to_high: ("EF BF C0", ConvertError::ToUtf8),
103        utf8_neg_4bytes_out_of_range_to_low: ("F0 90 80 7F", ConvertError::ToUtf8),
104        utf8_neg_4bytes_out_of_range_to_high: ("F4 8F BF C0", ConvertError::ToUtf8),
105
106        hex_neg_odd_char: ("8", ConvertError::HexStringToBytes),
107        hex_neg_invalid_char: ("FG", ConvertError::HexStringToBytes),
108    }
109}