Skip to main content

lexe_byte_array/
lib.rs

1use std::{
2    array::TryFromSliceError,
3    fmt::{self, Debug, Display},
4    hash::Hash,
5};
6
7// Re-export so we can cleanly use `lexe_hex` in the
8// `impl_fromstr_fromhex!` macro without dependent crates needing a random
9// `lexe_hex` dep.
10#[doc(hidden)]
11pub use lexe_hex;
12use lexe_hex::hex::{self, FromHex, HexDisplay};
13
14/// A trait for types represented in memory as a byte array. Should NOT be
15/// implemented for types that require validation of the byte array contents.
16pub trait ByteArray<const N: usize>: Copy + Debug + Eq + Hash + Sized {
17    // --- Required: array --- //
18
19    fn from_array(array: [u8; N]) -> Self;
20    fn to_array(&self) -> [u8; N];
21    fn as_array(&self) -> &[u8; N];
22
23    // --- Provided: array / slice / vec --- //
24
25    fn as_slice(&self) -> &[u8] {
26        self.as_array().as_slice()
27    }
28    fn to_vec(&self) -> Vec<u8> {
29        self.as_slice().to_vec()
30    }
31    fn try_from_slice(slice: &[u8]) -> Result<Self, TryFromSliceError> {
32        <[u8; N]>::try_from(slice).map(Self::from_array)
33    }
34    fn try_from_vec(vec: Vec<u8>) -> Result<Self, TryFromSliceError> {
35        Self::try_from_slice(&vec)
36    }
37
38    // --- Provided: hex --- //
39
40    fn from_hex(s: &str) -> Result<Self, hex::DecodeError> {
41        <[u8; N]>::from_hex(s).map(Self::from_array)
42    }
43    fn to_hex(&self) -> String {
44        hex::encode(self.as_slice())
45    }
46    fn as_hex_display(&self) -> HexDisplay<'_> {
47        hex::display(self.as_slice())
48    }
49    fn fmt_as_hex(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        Display::fmt(&self.as_hex_display(), f)
51    }
52}
53
54/// Impls [`ByteArray`] for a transparent newtype over `[u8; N]`
55///
56/// ```ignore
57/// lexe_byte_array::impl_byte_array!(Measurement, 32);
58/// ```
59#[macro_export]
60macro_rules! impl_byte_array {
61    ($type:ty, $n:expr) => {
62        impl ByteArray<$n> for $type {
63            fn from_array(array: [u8; $n]) -> Self {
64                Self(array)
65            }
66            fn to_array(&self) -> [u8; $n] {
67                self.0
68            }
69            fn as_array(&self) -> &[u8; $n] {
70                &self.0
71            }
72        }
73
74        impl AsRef<[u8]> for $type {
75            fn as_ref(&self) -> &[u8] {
76                self.0.as_slice()
77            }
78        }
79
80        impl AsRef<[u8; $n]> for $type {
81            fn as_ref(&self) -> &[u8; $n] {
82                &self.0
83            }
84        }
85    };
86}
87
88/// Impls `FromStr` and `FromHex` for a [`ByteArray`] parsed from a hex string.
89///
90/// ```ignore
91/// lexe_byte_array::impl_fromstr_fromhex!(Measurement);
92/// ```
93#[macro_export]
94macro_rules! impl_fromstr_fromhex {
95    ($type:ty, $n:expr) => {
96        impl std::str::FromStr for $type {
97            type Err = $crate::lexe_hex::hex::DecodeError;
98            fn from_str(s: &str) -> Result<Self, Self::Err> {
99                <Self as ByteArray<$n>>::from_hex(s)
100            }
101        }
102        impl $crate::lexe_hex::hex::FromHex for $type {
103            fn from_hex(
104                s: &str,
105            ) -> Result<Self, $crate::lexe_hex::hex::DecodeError> {
106                <[u8; $n]>::from_hex(s).map(Self::from_array)
107            }
108        }
109    };
110}
111
112/// Impls Debug + Display for a [`ByteArray`] type formatted as a hex string.
113///
114/// ```ignore
115/// lexe_byte_array::impl_debug_display_hex!(Measurement);
116/// ```
117#[macro_export]
118macro_rules! impl_debug_display_as_hex {
119    ($type:ty) => {
120        impl std::fmt::Debug for $type {
121            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122                // We don't implement this like
123                // `f.debug_tuple(stringify!($type)).field(&self.0).finish()`
124                // because that includes useless newlines when pretty printing.
125                write!(
126                    f,
127                    "{}(\"{}\")",
128                    stringify!($type),
129                    self.as_hex_display()
130                )
131            }
132        }
133        impl std::fmt::Display for $type {
134            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135                Self::fmt_as_hex(self, f)
136            }
137        }
138    };
139}
140
141/// Impls Debug + Display with secret values redacted.
142/// Useful for preventing the accidental leakage of secrets in logs.
143/// Can technically be used for non [`ByteArray`] types as well.
144///
145/// ```ignore
146/// lexe_byte_array::impl_debug_display_redacted!(PaymentSecret);
147/// ```
148#[macro_export]
149macro_rules! impl_debug_display_redacted {
150    ($type:ty) => {
151        impl std::fmt::Debug for $type {
152            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153                f.write_str(concat!(stringify!($type), "(..)"))
154            }
155        }
156        impl std::fmt::Display for $type {
157            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158                f.write_str("..")
159            }
160        }
161    };
162}
163
164#[cfg(test)]
165mod test {
166    use std::str::FromStr;
167
168    use super::*;
169
170    #[derive(Copy, Clone, Eq, PartialEq, Hash)]
171    struct MyStruct([u8; 4]);
172
173    impl_byte_array!(MyStruct, 4);
174    impl_fromstr_fromhex!(MyStruct, 4);
175    impl_debug_display_as_hex!(MyStruct);
176
177    #[derive(Copy, Clone, Eq, PartialEq, Hash)]
178    struct MySecret([u8; 4]);
179
180    impl_byte_array!(MySecret, 4);
181    impl_fromstr_fromhex!(MySecret, 4);
182    impl_debug_display_redacted!(MySecret);
183
184    #[test]
185    fn test_display_and_debug() {
186        let data = [0xde, 0xad, 0xbe, 0xef];
187
188        // Test regular display/debug
189        let my_struct = MyStruct(data);
190        assert_eq!(my_struct.to_string(), "deadbeef");
191        assert_eq!(format!("{my_struct}"), "deadbeef");
192        assert_eq!(format!("{my_struct:#}"), "deadbeef");
193        assert_eq!(format!("{:?}", my_struct), r#"MyStruct("deadbeef")"#);
194        assert_eq!(format!("{:#?}", my_struct), r#"MyStruct("deadbeef")"#);
195        assert_eq!(my_struct.as_hex_display().to_string(), "deadbeef");
196        assert_eq!(my_struct.to_hex(), "deadbeef");
197
198        // Test redacted display/debug
199        let my_secret = MySecret(data);
200        assert_eq!(my_secret.to_string(), "..");
201        assert_eq!(format!("My secret is {my_secret}"), "My secret is ..");
202        assert_eq!(format!("My secret is {my_secret:#}"), "My secret is ..");
203        assert_eq!(format!("{:?}", my_secret), r#"MySecret(..)"#);
204        assert_eq!(format!("{:#?}", my_secret), r#"MySecret(..)"#);
205    }
206
207    #[test]
208    fn basic_parse() {
209        // Valid cases
210        let my_struct = MyStruct::from_str("deadbeef").unwrap();
211        assert_eq!(my_struct.0, [0xde, 0xad, 0xbe, 0xef]);
212
213        let my_secret = MySecret::from_str("deadbeef").unwrap();
214        assert_eq!(my_secret.0, [0xde, 0xad, 0xbe, 0xef]);
215
216        // Error cases
217        MyStruct::from_str("invalid").unwrap_err();
218        MyStruct::from_str("deadbee").unwrap_err(); // Too short
219        MyStruct::from_str("deadbeefff").unwrap_err(); // Too long
220        MyStruct::from_str("wxyz").unwrap_err(); // Not hex
221    }
222}