hex_conservative/
display.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Helpers for displaying bytes as hex strings.
4//!
5//! This module provides a trait for displaying things as hex as well as an implementation for
6//! `&[u8]`.
7//!
8//! For arrays and slices we support padding and precision for length < 512 bytes.
9//!
10//! # Examples
11//!
12//! ```
13//! use hex_conservative::DisplayHex;
14//!
15//! // Display as hex.
16//! let v = vec![0xde, 0xad, 0xbe, 0xef];
17//! assert_eq!(format!("{}", v.as_hex()), "deadbeef");
18//!
19//! // Get the most significant bytes.
20//! let v = vec![0x01, 0x23, 0x45, 0x67];
21//! assert_eq!(format!("{0:.4}", v.as_hex()), "0123");
22//!
23//! // Padding with zeros
24//! let v = vec![0xab; 2];
25//! assert_eq!(format!("{:0>8}", v.as_hex()), "0000abab");
26//!```
27
28#[cfg(all(feature = "alloc", not(feature = "std")))]
29use alloc::string::String;
30use core::borrow::Borrow;
31use core::fmt;
32
33use super::{Case, Table};
34use crate::buf_encoder::BufEncoder;
35
36/// Extension trait for types that can be displayed as hex.
37///
38/// Types that have a single, obvious text representation being hex should **not** implement this
39/// trait and simply implement `Display` instead.
40///
41/// This trait should be generally implemented for references only. We would prefer to use GAT but
42/// that is beyond our MSRV. As a lint we require the `IsRef` trait which is implemented for all
43/// references.
44pub trait DisplayHex: Copy + sealed::IsRef {
45    /// The type providing [`fmt::Display`] implementation.
46    ///
47    /// This is usually a wrapper type holding a reference to `Self`.
48    type Display: fmt::Display + fmt::Debug + fmt::LowerHex + fmt::UpperHex;
49
50    /// Display `Self` as a continuous sequence of ASCII hex chars.
51    fn as_hex(self) -> Self::Display;
52
53    /// Create a lower-hex-encoded string.
54    ///
55    /// A shorthand for `to_hex_string(Case::Lower)`, so that `Case` doesn't need to be imported.
56    ///
57    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
58    #[cfg(feature = "alloc")]
59    fn to_lower_hex_string(self) -> String { self.to_hex_string(Case::Lower) }
60
61    /// Create an upper-hex-encoded string.
62    ///
63    /// A shorthand for `to_hex_string(Case::Upper)`, so that `Case` doesn't need to be imported.
64    ///
65    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
66    #[cfg(feature = "alloc")]
67    fn to_upper_hex_string(self) -> String { self.to_hex_string(Case::Upper) }
68
69    /// Create a hex-encoded string.
70    ///
71    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
72    #[cfg(feature = "alloc")]
73    fn to_hex_string(self, case: Case) -> String {
74        let mut string = String::new();
75        self.append_hex_to_string(case, &mut string);
76        string
77    }
78
79    /// Appends hex-encoded content to an existing `String`.
80    ///
81    /// This may be faster than `write!(string, "{:x}", self.as_hex())` because it uses
82    /// `hex_reserve_sugggestion`.
83    #[cfg(feature = "alloc")]
84    fn append_hex_to_string(self, case: Case, string: &mut String) {
85        use fmt::Write;
86
87        string.reserve(self.hex_reserve_suggestion());
88        match case {
89            Case::Lower => write!(string, "{:x}", self.as_hex()),
90            Case::Upper => write!(string, "{:X}", self.as_hex()),
91        }
92        .unwrap_or_else(|_| {
93            let name = core::any::type_name::<Self::Display>();
94            // We don't expect `std` to ever be buggy, so the bug is most likely in the `Display`
95            // impl of `Self::Display`.
96            panic!("The implementation of Display for {} returned an error when it shouldn't", name)
97        })
98    }
99
100    /// Hints how much bytes to reserve when creating a `String`.
101    ///
102    /// Implementors that know the number of produced bytes upfront should override this.
103    /// Defaults to 0.
104    ///
105    // We prefix the name with `hex_` to avoid potential collision with other methods.
106    fn hex_reserve_suggestion(self) -> usize { 0 }
107}
108
109fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Result {
110    use fmt::Write;
111    // There are at least two optimizations left:
112    //
113    // * Reusing the buffer (encoder) which may decrease the number of virtual calls
114    // * Not recursing, avoiding another 1024B allocation and zeroing
115    //
116    // This would complicate the code so I was too lazy to do them but feel free to send a PR!
117
118    let mut encoder = BufEncoder::<1024>::new(case);
119
120    let pad_right = if let Some(width) = f.width() {
121        let string_len = match f.precision() {
122            Some(max) => core::cmp::min(max, bytes.len() * 2),
123            None => bytes.len() * 2,
124        };
125
126        if string_len < width {
127            let (left, right) = match f.align().unwrap_or(fmt::Alignment::Left) {
128                fmt::Alignment::Left => (0, width - string_len),
129                fmt::Alignment::Right => (width - string_len, 0),
130                fmt::Alignment::Center => ((width - string_len) / 2, (width - string_len + 1) / 2),
131            };
132            // Avoid division by zero and optimize for common case.
133            if left > 0 {
134                let c = f.fill();
135                let chunk_len = encoder.put_filler(c, left);
136                let padding = encoder.as_str();
137                for _ in 0..(left / chunk_len) {
138                    f.write_str(padding)?;
139                }
140                f.write_str(&padding[..((left % chunk_len) * c.len_utf8())])?;
141                encoder.clear();
142            }
143            right
144        } else {
145            0
146        }
147    } else {
148        0
149    };
150
151    match f.precision() {
152        Some(max) if bytes.len() > max / 2 => {
153            write!(f, "{}", bytes[..(max / 2)].as_hex())?;
154            if max % 2 == 1 {
155                f.write_char(case.table().byte_to_chars(bytes[max / 2])[0])?;
156            }
157        }
158        Some(_) | None => {
159            let mut chunks = bytes.chunks_exact(512);
160            for chunk in &mut chunks {
161                encoder.put_bytes(chunk);
162                f.write_str(encoder.as_str())?;
163                encoder.clear();
164            }
165            encoder.put_bytes(chunks.remainder());
166            f.write_str(encoder.as_str())?;
167        }
168    }
169
170    // Avoid division by zero and optimize for common case.
171    if pad_right > 0 {
172        encoder.clear();
173        let c = f.fill();
174        let chunk_len = encoder.put_filler(c, pad_right);
175        let padding = encoder.as_str();
176        for _ in 0..(pad_right / chunk_len) {
177            f.write_str(padding)?;
178        }
179        f.write_str(&padding[..((pad_right % chunk_len) * c.len_utf8())])?;
180    }
181    Ok(())
182}
183
184mod sealed {
185    /// Trait marking a shared reference.
186    pub trait IsRef: Copy {}
187
188    impl<T: ?Sized> IsRef for &'_ T {}
189}
190
191impl<'a> DisplayHex for &'a [u8] {
192    type Display = DisplayByteSlice<'a>;
193
194    #[inline]
195    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
196
197    #[inline]
198    fn hex_reserve_suggestion(self) -> usize {
199        // Since the string wouldn't fit into address space if this overflows (actually even for
200        // smaller amounts) it's better to panic right away. It should also give the optimizer
201        // better opportunities.
202        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
203    }
204}
205
206#[cfg(feature = "alloc")]
207impl<'a> DisplayHex for &'a alloc::vec::Vec<u8> {
208    type Display = DisplayByteSlice<'a>;
209
210    #[inline]
211    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
212
213    #[inline]
214    fn hex_reserve_suggestion(self) -> usize {
215        // Since the string wouldn't fit into address space if this overflows (actually even for
216        // smaller amounts) it's better to panic right away. It should also give the optimizer
217        // better opportunities.
218        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
219    }
220}
221
222/// Displays byte slice as hex.
223///
224/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex).
225pub struct DisplayByteSlice<'a> {
226    // pub because we want to keep lengths in sync
227    pub(crate) bytes: &'a [u8],
228}
229
230impl<'a> DisplayByteSlice<'a> {
231    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
232        internal_display(self.bytes, f, case)
233    }
234}
235
236impl<'a> fmt::Display for DisplayByteSlice<'a> {
237    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
238}
239
240impl<'a> fmt::Debug for DisplayByteSlice<'a> {
241    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
242}
243
244impl<'a> fmt::LowerHex for DisplayByteSlice<'a> {
245    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
246}
247
248impl<'a> fmt::UpperHex for DisplayByteSlice<'a> {
249    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
250}
251
252/// Displays byte array as hex.
253///
254/// Created by [`<&[u8; CAP / 2] as DisplayHex>::as_hex`](DisplayHex::as_hex).
255pub struct DisplayArray<'a, const CAP: usize> {
256    array: &'a [u8],
257}
258
259impl<'a, const CAP: usize> DisplayArray<'a, CAP> {
260    /// Creates the wrapper.
261    ///
262    /// # Panics
263    ///
264    /// When the length of array is greater than capacity / 2.
265    #[inline]
266    fn new(array: &'a [u8]) -> Self {
267        assert!(array.len() <= CAP / 2);
268        DisplayArray { array }
269    }
270
271    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
272        internal_display(self.array, f, case)
273    }
274}
275
276impl<'a, const LEN: usize> fmt::Display for DisplayArray<'a, LEN> {
277    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
278}
279
280impl<'a, const LEN: usize> fmt::Debug for DisplayArray<'a, LEN> {
281    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
282}
283
284impl<'a, const LEN: usize> fmt::LowerHex for DisplayArray<'a, LEN> {
285    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
286}
287
288impl<'a, const LEN: usize> fmt::UpperHex for DisplayArray<'a, LEN> {
289    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
290}
291
292macro_rules! impl_array_as_hex {
293    ($($len:expr),*) => {
294        $(
295            impl<'a> DisplayHex for &'a [u8; $len] {
296                type Display = DisplayArray<'a, {$len * 2}>;
297
298                fn as_hex(self) -> Self::Display {
299                    DisplayArray::new(self)
300                }
301            }
302        )*
303    }
304}
305
306impl_array_as_hex!(
307    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 32, 33, 64, 65, 128, 256, 512, 1024,
308    2048, 4096
309);
310
311/// Format known-length array as hex.
312///
313/// This supports all formatting options of formatter and may be faster than calling `as_hex()` on
314/// an arbitrary `&[u8]`. Note that the implementation intentionally keeps leading zeros even when
315/// not requested. This is designed to display values such as hashes and keys and removing leading
316/// zeros would be confusing.
317///
318/// Note that the bytes parameter is `IntoIterator` this means that if you would like to do some
319/// manipulation to the byte array before formatting then you can. For example `bytes.iter().rev()`
320/// to print the array backwards.
321///
322/// ## Parameters
323///
324/// * `$formatter` - a [`fmt::Formatter`].
325/// * `$len` known length of `$bytes`, must be a const expression.
326/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
327/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case.
328///
329/// ## Panics
330///
331/// This macro panics if `$len` is not equal to `$bytes.len()`. It also fails to compile if `$len`
332/// is more than half of `usize::MAX`.
333#[macro_export]
334macro_rules! fmt_hex_exact {
335    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
336        // statically check $len
337        #[allow(deprecated)]
338        const _: () = [()][($len > usize::MAX / 2) as usize];
339        assert_eq!($bytes.len(), $len);
340        $crate::display::fmt_hex_exact_fn::<_, { $len * 2 }>($formatter, $bytes, $case)
341    }};
342}
343pub use fmt_hex_exact;
344
345/// Adds `core::fmt` trait implementations to type `$ty`.
346///
347/// Implements:
348///
349/// - `fmt::{LowerHex, UpperHex}` using [`fmt_hex_exact`].
350/// - `fmt::{Display, Debug}` by calling `LowerHex`.
351///
352/// Requires:
353///
354/// - `$ty` must implement `IntoIterator<Item=Borrow<u8>>`.
355///
356/// ## Parameters
357///
358/// * `$ty` - the type to implement traits on.
359/// * `$len` - known length of `$bytes`, must be a const expression.
360/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
361/// * `$reverse` - true if you want the array to be displayed backwards.
362/// * `$gen: $gent` - optional generic type(s) and trait bound(s) to put on `$ty` e.g, `F: Foo`.
363///
364/// ## Examples
365///
366/// ```
367/// # use core::borrow::Borrow;
368/// # use hex_conservative::impl_fmt_traits;
369/// struct Wrapper([u8; 4]);
370///
371/// impl Borrow<[u8]> for Wrapper {
372///     fn borrow(&self) -> &[u8] { &self.0[..] }
373/// }
374///
375/// impl_fmt_traits! {
376///     impl fmt_traits for Wrapper {
377///         const LENGTH: usize = 4;
378///     }
379/// }
380///
381/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
382/// assert_eq!(format!("{}", w), "12345678");
383/// ```
384///
385/// We support generics on `$ty`:
386///
387/// ```
388/// # use core::borrow::Borrow;
389/// # use core::marker::PhantomData;
390/// # use hex_conservative::impl_fmt_traits;
391/// struct Wrapper<T>([u8; 4], PhantomData<T>);
392///
393/// // `Clone` is just some arbitrary trait.
394/// impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
395///     fn borrow(&self) -> &[u8] { &self.0[..] }
396/// }
397///
398/// impl_fmt_traits! {
399///     impl<T: Clone> fmt_traits for Wrapper<T> {
400///         const LENGTH: usize = 4;
401///     }
402/// }
403///
404/// let w = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
405/// assert_eq!(format!("{}", w), "12345678");
406/// ```
407///
408/// And also, as is required by `rust-bitcoin`, we support displaying
409/// the hex string byte-wise backwards:
410///
411/// ```
412/// # use core::borrow::Borrow;
413/// # use hex_conservative::impl_fmt_traits;
414/// struct Wrapper([u8; 4]);
415///
416/// impl Borrow<[u8]> for Wrapper {
417///     fn borrow(&self) -> &[u8] { &self.0[..] }
418/// }
419///
420/// impl_fmt_traits! {
421///     #[display_backward(true)]
422///     impl fmt_traits for Wrapper {
423///         const LENGTH: usize = 4;
424///     }
425/// }
426/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
427/// assert_eq!(format!("{}", w), "78563412");
428/// ```
429#[macro_export]
430macro_rules! impl_fmt_traits {
431    // Without generic and trait bounds and without display_backward attribute.
432    (impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
433        $crate::impl_fmt_traits! {
434            #[display_backward(false)]
435            impl<> fmt_traits for $ty<> {
436                const LENGTH: usize = $len;
437            }
438        }
439    };
440    // Without generic and trait bounds and with display_backward attribute.
441    (#[display_backward($reverse:expr)] impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
442        $crate::impl_fmt_traits! {
443            #[display_backward($reverse)]
444            impl<> fmt_traits for $ty<> {
445                const LENGTH: usize = $len;
446            }
447        }
448    };
449    // With generic and trait bounds and without display_backward attribute.
450    (impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
451        $crate::impl_fmt_traits! {
452            #[display_backward(false)]
453            impl<$($gen: $gent),*> fmt_traits for $ty<$($unused),*> {
454                const LENGTH: usize = $len;
455            }
456        }
457    };
458    // With generic and trait bounds and display_backward attribute.
459    (#[display_backward($reverse:expr)] impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
460        impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
461            #[inline]
462            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
463                let case = $crate::Case::Lower;
464
465                if $reverse {
466                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
467                    $crate::fmt_hex_exact!(f, $len, bytes, case)
468                } else {
469                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
470                    $crate::fmt_hex_exact!(f, $len, bytes, case)
471                }
472            }
473        }
474
475        impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> {
476            #[inline]
477            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
478                let case = $crate::Case::Upper;
479
480                if $reverse {
481                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
482                    $crate::fmt_hex_exact!(f, $len, bytes, case)
483                } else {
484                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
485                    $crate::fmt_hex_exact!(f, $len, bytes, case)
486                }
487            }
488        }
489
490        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> {
491            #[inline]
492            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
493                $crate::_export::_core::fmt::LowerHex::fmt(self, f)
494            }
495        }
496
497        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> {
498            #[inline]
499            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
500                $crate::_export::_core::fmt::LowerHex::fmt(&self, f)
501            }
502        }
503    };
504}
505pub use impl_fmt_traits;
506
507// Implementation detail of `write_hex_exact` macro to de-duplicate the code
508//
509// Whether hex is an integer or a string is debatable, we cater a little bit to each.
510// - We support users adding `0x` prefix using "{:#}" (treating hex like an integer).
511// - We support limiting the output using precision "{:.10}" (treating hex like a string).
512//
513// This assumes `bytes.len() * 2 == N`.
514#[doc(hidden)]
515#[inline]
516pub fn fmt_hex_exact_fn<I, const N: usize>(
517    f: &mut fmt::Formatter,
518    bytes: I,
519    case: Case,
520) -> fmt::Result
521where
522    I: IntoIterator,
523    I::Item: Borrow<u8>,
524{
525    let mut encoder = BufEncoder::<N>::new(case);
526    let encoded = match f.precision() {
527        Some(p) if p < N => {
528            let n = (p + 1) / 2;
529            encoder.put_bytes(bytes.into_iter().take(n));
530            &encoder.as_str()[..p]
531        }
532        _ => {
533            encoder.put_bytes(bytes);
534            encoder.as_str()
535        }
536    };
537    f.pad_integral(true, "0x", encoded)
538}
539
540/// Given a `T:` [`fmt::Write`], `HexWriter` implements [`std::io::Write`]
541/// and writes the source bytes to its inner `T` as hex characters.
542#[cfg(any(test, feature = "std"))]
543#[cfg_attr(docsrs, doc(cfg(any(test, feature = "std"))))]
544pub struct HexWriter<T> {
545    writer: T,
546    table: &'static Table,
547}
548
549#[cfg(any(test, feature = "std"))]
550#[cfg_attr(docsrs, doc(cfg(any(test, feature = "std"))))]
551impl<T> HexWriter<T> {
552    /// Creates a `HexWriter` that writes the source bytes to `dest` as hex characters
553    /// in the given `case`.
554    pub fn new(dest: T, case: Case) -> Self { Self { writer: dest, table: case.table() } }
555    /// Consumes this `HexWriter` returning the inner `T`.
556    pub fn into_inner(self) -> T { self.writer }
557}
558
559#[cfg(any(test, feature = "std"))]
560#[cfg_attr(docsrs, doc(cfg(any(test, feature = "std"))))]
561impl<T> std::io::Write for HexWriter<T>
562where
563    T: core::fmt::Write,
564{
565    /// # Errors
566    ///
567    /// If no bytes could be written to this `HexWriter`, and the provided buffer is not empty,
568    /// returns [`std::io::ErrorKind::Other`], otherwise returns `Ok`.
569    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
570        let mut n = 0;
571        for byte in buf {
572            let mut hex_chars = [0u8; 2];
573            let hex_str = self.table.byte_to_str(&mut hex_chars, *byte);
574            if self.writer.write_str(hex_str).is_err() {
575                break;
576            }
577            n += 1;
578        }
579        if n == 0 && !buf.is_empty() {
580            Err(std::io::ErrorKind::Other.into())
581        } else {
582            Ok(n)
583        }
584    }
585    fn flush(&mut self) -> Result<(), std::io::Error> { Ok(()) }
586}
587
588#[cfg(test)]
589mod tests {
590    #[cfg(feature = "alloc")]
591    use super::*;
592
593    #[cfg(feature = "alloc")]
594    mod alloc {
595        use core::marker::PhantomData;
596
597        use super::*;
598
599        fn check_encoding(bytes: &[u8]) {
600            use core::fmt::Write;
601
602            let s1 = bytes.to_lower_hex_string();
603            let mut s2 = String::with_capacity(bytes.len() * 2);
604            for b in bytes {
605                write!(s2, "{:02x}", b).unwrap();
606            }
607            assert_eq!(s1, s2);
608        }
609
610        #[test]
611        fn empty() { check_encoding(b""); }
612
613        #[test]
614        fn single() { check_encoding(b"*"); }
615
616        #[test]
617        fn two() { check_encoding(b"*x"); }
618
619        #[test]
620        fn just_below_boundary() { check_encoding(&[42; 512]); }
621
622        #[test]
623        fn just_above_boundary() { check_encoding(&[42; 513]); }
624
625        #[test]
626        fn just_above_double_boundary() { check_encoding(&[42; 1025]); }
627
628        #[test]
629        fn fmt_exact_macro() {
630            use crate::alloc::string::ToString;
631
632            struct Dummy([u8; 32]);
633
634            impl fmt::Display for Dummy {
635                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
636                    fmt_hex_exact!(f, 32, &self.0, Case::Lower)
637                }
638            }
639            let dummy = Dummy([42; 32]);
640            assert_eq!(dummy.to_string(), "2a".repeat(32));
641            assert_eq!(format!("{:.10}", dummy), "2a".repeat(5));
642            assert_eq!(format!("{:.11}", dummy), "2a".repeat(5) + "2");
643            assert_eq!(format!("{:.65}", dummy), "2a".repeat(32));
644        }
645
646        macro_rules! test_display_hex {
647            ($fs: expr, $a: expr, $check: expr) => {
648                let to_display_array = $a;
649                let to_display_byte_slice = Vec::from($a);
650                assert_eq!(format!($fs, to_display_array.as_hex()), $check);
651                assert_eq!(format!($fs, to_display_byte_slice.as_hex()), $check);
652            };
653        }
654
655        #[test]
656        fn display_short_with_padding() {
657            test_display_hex!("Hello {:<8}!", [0xbe, 0xef], "Hello beef    !");
658            test_display_hex!("Hello {:-<8}!", [0xbe, 0xef], "Hello beef----!");
659            test_display_hex!("Hello {:^8}!", [0xbe, 0xef], "Hello   beef  !");
660            test_display_hex!("Hello {:>8}!", [0xbe, 0xef], "Hello     beef!");
661        }
662
663        #[test]
664        fn display_long() {
665            // Note this string is shorter than the one above.
666            let a = [0xab; 512];
667            let mut want = "0".repeat(2000 - 1024);
668            want.extend(core::iter::repeat("ab").take(512));
669            test_display_hex!("{:0>2000}", a, want);
670        }
671
672        // Precision and padding act the same as for strings in the stdlib (because we use `Formatter::pad`).
673
674        #[test]
675        fn precision_truncates() {
676            // Precision gets the most significant bytes.
677            // Remember the integer is number of hex chars not number of bytes.
678            test_display_hex!("{0:.4}", [0x12, 0x34, 0x56, 0x78], "1234");
679            test_display_hex!("{0:.5}", [0x12, 0x34, 0x56, 0x78], "12345");
680        }
681
682        #[test]
683        fn precision_with_padding_truncates() {
684            // Precision gets the most significant bytes.
685            test_display_hex!("{0:10.4}", [0x12, 0x34, 0x56, 0x78], "1234      ");
686            test_display_hex!("{0:10.5}", [0x12, 0x34, 0x56, 0x78], "12345     ");
687        }
688
689        #[test]
690        fn precision_with_padding_pads_right() {
691            test_display_hex!("{0:10.20}", [0x12, 0x34, 0x56, 0x78], "12345678  ");
692            test_display_hex!("{0:10.14}", [0x12, 0x34, 0x56, 0x78], "12345678  ");
693        }
694
695        #[test]
696        fn precision_with_padding_pads_left() {
697            test_display_hex!("{0:>10.20}", [0x12, 0x34, 0x56, 0x78], "  12345678");
698        }
699
700        #[test]
701        fn precision_with_padding_pads_center() {
702            test_display_hex!("{0:^10.20}", [0x12, 0x34, 0x56, 0x78], " 12345678 ");
703        }
704
705        #[test]
706        fn precision_with_padding_pads_center_odd() {
707            test_display_hex!("{0:^11.20}", [0x12, 0x34, 0x56, 0x78], " 12345678  ");
708        }
709
710        #[test]
711        fn precision_does_not_extend() {
712            test_display_hex!("{0:.16}", [0x12, 0x34, 0x56, 0x78], "12345678");
713        }
714
715        #[test]
716        fn padding_extends() {
717            test_display_hex!("{:0>8}", [0xab; 2], "0000abab");
718        }
719
720        #[test]
721        fn padding_does_not_truncate() {
722            test_display_hex!("{:0>4}", [0x12, 0x34, 0x56, 0x78], "12345678");
723        }
724
725        #[test]
726        fn hex_fmt_impl_macro_forward() {
727            struct Wrapper([u8; 4]);
728
729            impl Borrow<[u8]> for Wrapper {
730                fn borrow(&self) -> &[u8] { &self.0[..] }
731            }
732
733            impl_fmt_traits! {
734                #[display_backward(false)]
735                impl fmt_traits for Wrapper {
736                    const LENGTH: usize = 4;
737                }
738            }
739
740            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
741
742            let want = "12345678";
743            let got = format!("{}", tc);
744            assert_eq!(got, want);
745        }
746
747        #[test]
748        fn hex_fmt_impl_macro_backwards() {
749            struct Wrapper([u8; 4]);
750
751            impl Borrow<[u8]> for Wrapper {
752                fn borrow(&self) -> &[u8] { &self.0[..] }
753            }
754
755            impl_fmt_traits! {
756                #[display_backward(true)]
757                impl fmt_traits for Wrapper {
758                    const LENGTH: usize = 4;
759                }
760            }
761
762            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
763
764            let want = "78563412";
765            let got = format!("{}", tc);
766            assert_eq!(got, want);
767        }
768
769        #[test]
770        fn hex_fmt_impl_macro_gen_forward() {
771            struct Wrapper<T>([u8; 4], PhantomData<T>);
772
773            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
774                fn borrow(&self) -> &[u8] { &self.0[..] }
775            }
776
777            impl_fmt_traits! {
778                #[display_backward(false)]
779                impl<T: Clone> fmt_traits for Wrapper<T> {
780                    const LENGTH: usize = 4;
781                }
782            }
783
784            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
785            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
786
787            let want = "12345678";
788            let got = format!("{}", tc);
789            assert_eq!(got, want);
790        }
791
792        #[test]
793        fn hex_fmt_impl_macro_gen_backwards() {
794            struct Wrapper<T>([u8; 4], PhantomData<T>);
795
796            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
797                fn borrow(&self) -> &[u8] { &self.0[..] }
798            }
799
800            impl_fmt_traits! {
801                #[display_backward(true)]
802                impl<T: Clone> fmt_traits for Wrapper<T> {
803                    const LENGTH: usize = 4;
804                }
805            }
806
807            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
808            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
809
810            let want = "78563412";
811            let got = format!("{}", tc);
812            assert_eq!(got, want);
813        }
814    }
815
816    #[cfg(feature = "std")]
817    mod std {
818
819        #[test]
820        fn hex_writer() {
821            use std::io::{ErrorKind, Result, Write};
822
823            use arrayvec::ArrayString;
824
825            use super::Case::{Lower, Upper};
826            use super::{DisplayHex, HexWriter};
827
828            macro_rules! test_hex_writer {
829                ($cap:expr, $case: expr, $src: expr, $want: expr, $hex_result: expr) => {
830                    let dest_buf = ArrayString::<$cap>::new();
831                    let mut dest = HexWriter::new(dest_buf, $case);
832                    let got = dest.write($src);
833                    match $want {
834                        Ok(n) => assert_eq!(got.unwrap(), n),
835                        Err(e) => assert_eq!(got.unwrap_err().kind(), e.kind()),
836                    }
837                    assert_eq!(dest.into_inner().as_str(), $hex_result);
838                };
839            }
840
841            test_hex_writer!(0, Lower, &[], Result::Ok(0), "");
842            test_hex_writer!(0, Lower, &[0xab, 0xcd], Result::Err(ErrorKind::Other.into()), "");
843            test_hex_writer!(1, Lower, &[0xab, 0xcd], Result::Err(ErrorKind::Other.into()), "");
844            test_hex_writer!(2, Lower, &[0xab, 0xcd], Result::Ok(1), "ab");
845            test_hex_writer!(3, Lower, &[0xab, 0xcd], Result::Ok(1), "ab");
846            test_hex_writer!(4, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd");
847            test_hex_writer!(8, Lower, &[0xab, 0xcd], Result::Ok(2), "abcd");
848            test_hex_writer!(8, Upper, &[0xab, 0xcd], Result::Ok(2), "ABCD");
849
850            let vec: Vec<_> = (0u8..32).collect();
851            let mut writer = HexWriter::new(String::new(), Lower);
852            writer.write_all(&vec[..]).unwrap();
853            assert_eq!(writer.into_inner(), vec.to_lower_hex_string());
854        }
855    }
856}