Skip to main content

byte_wrapper/
hex_array.rs

1// Copyright (c) The byte-wrapper Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! The [`HexArray`] newtype wrapper.
5
6use crate::{HexSlice, hex_slice::HexDisplay};
7use core::{array::TryFromSliceError, error, fmt, str::FromStr};
8
9/// A byte array that displays and parses as hex.
10///
11/// `HexArray<N>` wraps `[u8; N]`, providing [`Display`](fmt::Display),
12/// [`FromStr`], [`LowerHex`](fmt::LowerHex), and
13/// [`UpperHex`](fmt::UpperHex) implementations that use hexadecimal
14/// encoding.
15///
16/// With the **`serde`** feature enabled, it also implements
17/// `Serialize` and `Deserialize` (hex strings in human-readable
18/// formats, raw bytes in binary formats), and can be used with
19/// `#[serde(with = "HexArray::<N>")]` on `[u8; N]` fields.
20///
21/// # Examples
22///
23/// ```
24/// use byte_wrapper::HexArray;
25///
26/// let h = HexArray::new([0x01, 0x02, 0xab, 0xff]);
27/// assert_eq!(h.to_string(), "0102abff");
28///
29/// let parsed: HexArray<4> = "0102abff".parse().unwrap();
30/// assert_eq!(parsed, h);
31/// ```
32#[must_use]
33#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct HexArray<const N: usize>(pub [u8; N]);
35
36impl<const N: usize> Default for HexArray<N> {
37    fn default() -> Self {
38        Self([0u8; N])
39    }
40}
41
42impl<const N: usize> HexArray<N> {
43    /// Creates a new `HexArray` from a byte array.
44    #[inline]
45    pub const fn new(bytes: [u8; N]) -> Self {
46        Self(bytes)
47    }
48
49    /// Returns the inner byte array.
50    #[inline]
51    #[must_use]
52    pub const fn into_inner(self) -> [u8; N] {
53        self.0
54    }
55}
56
57/// Error returned by [`HexArray::from_str`] and
58/// [`HexVec::from_str`](crate::HexVec).
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum ParseHexError {
61    /// The input string had the wrong length.
62    InvalidLength {
63        /// Expected number of hex characters.
64        expected: usize,
65        /// Actual number of characters in the input.
66        actual: usize,
67    },
68    /// The input string had an odd number of characters.
69    OddLength {
70        /// Actual number of characters in the input.
71        actual: usize,
72    },
73    /// The input contained an invalid hex character.
74    InvalidHexCharacter {
75        /// The invalid character.
76        c: char,
77        /// Byte index of the invalid character in the input.
78        index: usize,
79    },
80}
81
82impl fmt::Display for ParseHexError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            ParseHexError::InvalidLength { expected, actual } => {
86                write!(
87                    f,
88                    "expected {} hex characters, got {}",
89                    expected, actual,
90                )
91            }
92            ParseHexError::OddLength { actual } => {
93                write!(
94                    f,
95                    "expected an even number of hex characters, got {actual}",
96                )
97            }
98            ParseHexError::InvalidHexCharacter { c, index } => {
99                write!(f, "invalid hex character '{}' at index {}", c, index,)
100            }
101        }
102    }
103}
104
105impl error::Error for ParseHexError {}
106
107/// Decodes `s` into `out`, requiring that `s` is exactly `2 * out.len()` hex
108/// characters.
109pub(crate) fn decode_hex_into(
110    s: &str,
111    out: &mut [u8],
112) -> Result<(), ParseHexError> {
113    let expected = out.len() * 2;
114    if s.len() != expected {
115        return Err(ParseHexError::InvalidLength { expected, actual: s.len() });
116    }
117    hex::decode_to_slice(s, out).map_err(|e| {
118        match e {
119            hex::FromHexError::InvalidHexCharacter { c, index } => {
120                ParseHexError::InvalidHexCharacter { c, index }
121            }
122            // The length is already validated above, so this
123            // branch is unreachable in practice.
124            hex::FromHexError::OddLength
125            | hex::FromHexError::InvalidStringLength => {
126                ParseHexError::InvalidLength { expected, actual: s.len() }
127            }
128        }
129    })
130}
131
132impl<const N: usize> FromStr for HexArray<N> {
133    type Err = ParseHexError;
134
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        let mut out = [0u8; N];
137        decode_hex_into(s, &mut out)?;
138        Ok(Self(out))
139    }
140}
141
142impl<const N: usize> fmt::Debug for HexArray<N> {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        f.debug_tuple("HexArray").field(&HexDisplay(&self.0)).finish()
145    }
146}
147
148impl<const N: usize> fmt::Display for HexArray<N> {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        fmt::Display::fmt(HexSlice::new(&self.0), f)
151    }
152}
153
154impl<const N: usize> fmt::LowerHex for HexArray<N> {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        fmt::LowerHex::fmt(HexSlice::new(&self.0), f)
157    }
158}
159
160impl<const N: usize> fmt::UpperHex for HexArray<N> {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        fmt::UpperHex::fmt(HexSlice::new(&self.0), f)
163    }
164}
165
166impl<const N: usize> core::ops::Deref for HexArray<N> {
167    type Target = [u8; N];
168
169    #[inline]
170    fn deref(&self) -> &Self::Target {
171        &self.0
172    }
173}
174
175impl<const N: usize> core::ops::DerefMut for HexArray<N> {
176    #[inline]
177    fn deref_mut(&mut self) -> &mut Self::Target {
178        &mut self.0
179    }
180}
181
182impl<const N: usize> AsRef<[u8]> for HexArray<N> {
183    #[inline]
184    fn as_ref(&self) -> &[u8] {
185        &self.0
186    }
187}
188
189impl<const N: usize> AsMut<[u8]> for HexArray<N> {
190    #[inline]
191    fn as_mut(&mut self) -> &mut [u8] {
192        &mut self.0
193    }
194}
195
196impl<const N: usize> From<[u8; N]> for HexArray<N> {
197    #[inline]
198    fn from(bytes: [u8; N]) -> Self {
199        Self(bytes)
200    }
201}
202
203impl<const N: usize> From<HexArray<N>> for [u8; N] {
204    #[inline]
205    fn from(hex_array: HexArray<N>) -> Self {
206        hex_array.0
207    }
208}
209
210impl<const N: usize> TryFrom<&[u8]> for HexArray<N> {
211    type Error = TryFromSliceError;
212
213    #[inline]
214    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
215        <[u8; N]>::try_from(slice).map(Self)
216    }
217}
218
219#[cfg(feature = "serde")]
220mod serde_impls {
221    use super::HexArray;
222    use crate::HexSlice;
223    use core::fmt;
224    use serde_core::{
225        Deserializer, Serialize,
226        de::{Expected, SeqAccess, Visitor},
227    };
228
229    struct HexExpected<const N: usize>;
230
231    impl<const N: usize> Expected for HexExpected<N> {
232        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233            write!(f, "a byte array [u8; {}]", N)
234        }
235    }
236
237    struct HexStrExpected<const N: usize>;
238
239    impl<const N: usize> Expected for HexStrExpected<N> {
240        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241            write!(f, "a hex string {} hex digits long", N * 2)
242        }
243    }
244
245    /// Deserializes hex strings (if human-readable) or byte arrays
246    /// (if not) to `[u8; N]`.
247    fn deserialize<'de, D, const N: usize>(
248        deserializer: D,
249    ) -> Result<[u8; N], D::Error>
250    where
251        D: Deserializer<'de>,
252    {
253        use serde_core::de::Error;
254
255        if deserializer.is_human_readable() {
256            // hex::FromHex doesn't have an implementation for
257            // const-generic N, so do our own thing.
258            struct HexVisitor<const N: usize>;
259
260            impl<'de2, const N: usize> Visitor<'de2> for HexVisitor<N> {
261                type Value = [u8; N];
262
263                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
264                    write!(f, "a hex string {} hex digits long", N * 2)
265                }
266
267                fn visit_str<E>(self, data: &str) -> Result<Self::Value, E>
268                where
269                    E: Error,
270                {
271                    let expected_len = N * 2;
272                    if data.len() != expected_len {
273                        return Err(E::invalid_length(
274                            data.len(),
275                            &HexStrExpected::<N>,
276                        ));
277                    }
278                    let mut out = [0u8; N];
279                    hex::decode_to_slice(data, &mut out)
280                        .map_err(Error::custom)?;
281                    Ok(out)
282                }
283            }
284
285            deserializer.deserialize_str(HexVisitor)
286        } else {
287            struct BytesVisitor<const N: usize>;
288
289            impl<'de2, const N: usize> Visitor<'de2> for BytesVisitor<N> {
290                type Value = [u8; N];
291
292                fn expecting(
293                    &self,
294                    formatter: &mut fmt::Formatter,
295                ) -> fmt::Result {
296                    write!(formatter, "a byte array [u8; {}]", N)
297                }
298
299                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
300                where
301                    E: Error,
302                {
303                    v.try_into().map_err(|_| {
304                        E::invalid_length(v.len(), &HexExpected::<N>)
305                    })
306                }
307
308                fn visit_seq<A>(
309                    self,
310                    mut seq: A,
311                ) -> Result<Self::Value, A::Error>
312                where
313                    A: SeqAccess<'de2>,
314                {
315                    // Reject early if the sequence reports a
316                    // wrong length.
317                    if let Some(len) = seq.size_hint()
318                        && len != N
319                    {
320                        return Err(Error::invalid_length(
321                            len,
322                            &HexExpected::<N>,
323                        ));
324                    }
325                    let mut out = [0u8; N];
326                    for (i, byte) in out.iter_mut().enumerate() {
327                        *byte = seq.next_element()?.ok_or_else(|| {
328                            Error::invalid_length(i, &HexExpected::<N>)
329                        })?;
330                    }
331                    // Reject trailing elements rather than
332                    // silently discarding them.
333                    if seq.next_element::<u8>()?.is_some() {
334                        // We don't know the actual length, but
335                        // we know it's more than N.
336                        return Err(Error::invalid_length(
337                            N + 1,
338                            &HexExpected::<N>,
339                        ));
340                    }
341                    Ok(out)
342                }
343            }
344
345            deserializer.deserialize_bytes(BytesVisitor)
346        }
347    }
348
349    impl<const N: usize> HexArray<N> {
350        /// Serializes a byte array as hex in human-readable formats,
351        /// or as raw bytes otherwise.
352        ///
353        /// Intended for use with
354        /// `#[serde(with = "HexArray::<N>")]`.
355        ///
356        /// # Examples
357        ///
358        /// ```
359        /// use byte_wrapper::HexArray;
360        /// use serde::{Deserialize, Serialize};
361        ///
362        /// #[derive(Serialize, Deserialize)]
363        /// struct Record {
364        ///     #[serde(with = "HexArray::<4>")]
365        ///     id: [u8; 4],
366        /// }
367        ///
368        /// let r = Record { id: [0x01, 0x02, 0x03, 0x04] };
369        /// let json = serde_json::to_string(&r).unwrap();
370        /// assert_eq!(json, r#"{"id":"01020304"}"#);
371        /// ```
372        #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
373        pub fn serialize<S>(
374            bytes: &[u8; N],
375            serializer: S,
376        ) -> Result<S::Ok, S::Error>
377        where
378            S: serde_core::Serializer,
379        {
380            HexSlice::new(bytes).serialize(serializer)
381        }
382
383        /// Deserializes a byte array from hex if the format is
384        /// human-readable, or as raw bytes otherwise.
385        ///
386        /// Intended for use with
387        /// `#[serde(with = "HexArray::<N>")]`.
388        ///
389        /// # Examples
390        ///
391        /// ```
392        /// use byte_wrapper::HexArray;
393        /// use serde::{Deserialize, Serialize};
394        ///
395        /// #[derive(Serialize, Deserialize)]
396        /// struct Record {
397        ///     #[serde(with = "HexArray::<4>")]
398        ///     id: [u8; 4],
399        /// }
400        ///
401        /// let r: Record = serde_json::from_str(r#"{"id":"01020304"}"#).unwrap();
402        /// assert_eq!(r.id, [0x01, 0x02, 0x03, 0x04]);
403        /// ```
404        #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
405        pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; N], D::Error>
406        where
407            D: Deserializer<'de>,
408        {
409            deserialize(deserializer)
410        }
411    }
412
413    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
414    impl<const N: usize> serde_core::Serialize for HexArray<N> {
415        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
416        where
417            S: serde_core::Serializer,
418        {
419            HexSlice::new(&self.0).serialize(serializer)
420        }
421    }
422
423    #[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
424    impl<'de, const N: usize> serde_core::Deserialize<'de> for HexArray<N> {
425        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426        where
427            D: Deserializer<'de>,
428        {
429            deserialize(deserializer).map(Self)
430        }
431    }
432}
433
434#[cfg(feature = "schemars08")]
435mod schemars_impls {
436    use super::HexArray;
437    use crate::schemars_util::x_rust_type_extension;
438    use alloc::{boxed::Box, format, string::String};
439    use schemars08::{
440        JsonSchema,
441        r#gen::SchemaGenerator,
442        schema::{InstanceType, Schema, SchemaObject, StringValidation},
443    };
444
445    impl<const N: usize> JsonSchema for HexArray<N> {
446        fn schema_name() -> String {
447            format!("HexArray_{N}")
448        }
449
450        fn is_referenceable() -> bool {
451            false
452        }
453
454        fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
455            let hex_len = N * 2;
456            Schema::Object(SchemaObject {
457                instance_type: Some(InstanceType::String.into()),
458                string: Some(Box::new(StringValidation {
459                    min_length: Some(hex_len as u32),
460                    max_length: Some(hex_len as u32),
461                    pattern: Some(format!("^[0-9a-fA-F]{{{hex_len}}}$")),
462                })),
463                extensions: x_rust_type_extension(&format!("HexArray::<{N}>")),
464                ..Default::default()
465            })
466        }
467    }
468}