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