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;
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
109mod sealed {
110    /// Trait marking a shared reference.
111    pub trait IsRef: Copy {}
112
113    impl<T: ?Sized> IsRef for &'_ T {}
114}
115
116impl<'a> DisplayHex for &'a [u8] {
117    type Display = DisplayByteSlice<'a>;
118
119    #[inline]
120    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
121
122    #[inline]
123    fn hex_reserve_suggestion(self) -> usize {
124        // Since the string wouldn't fit into address space if this overflows (actually even for
125        // smaller amounts) it's better to panic right away. It should also give the optimizer
126        // better opportunities.
127        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
128    }
129}
130
131#[cfg(feature = "alloc")]
132impl<'a> DisplayHex for &'a alloc::vec::Vec<u8> {
133    type Display = DisplayByteSlice<'a>;
134
135    #[inline]
136    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
137
138    #[inline]
139    fn hex_reserve_suggestion(self) -> usize {
140        // Since the string wouldn't fit into address space if this overflows (actually even for
141        // smaller amounts) it's better to panic right away. It should also give the optimizer
142        // better opportunities.
143        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
144    }
145}
146
147/// Displays byte slice as hex.
148///
149/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex).
150pub struct DisplayByteSlice<'a> {
151    // pub because we want to keep lengths in sync
152    pub(crate) bytes: &'a [u8],
153}
154
155impl<'a> DisplayByteSlice<'a> {
156    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
157        use fmt::Write;
158        // There are at least two optimizations left:
159        //
160        // * Reusing the buffer (encoder) which may decrease the number of virtual calls
161        // * Not recursing, avoiding another 1024B allocation and zeroing
162        //
163        // This would complicate the code so I was too lazy to do them but feel free to send a PR!
164
165        let mut encoder = BufEncoder::<1024>::new();
166
167        let pad_right = if let Some(width) = f.width() {
168            let string_len = match f.precision() {
169                Some(max) if self.bytes.len() * 2 > (max + 1) / 2 => max,
170                Some(_) | None => self.bytes.len() * 2,
171            };
172
173            if string_len < width {
174                let (left, right) = match f.align().unwrap_or(fmt::Alignment::Left) {
175                    fmt::Alignment::Left => (0, width - string_len),
176                    fmt::Alignment::Right => (width - string_len, 0),
177                    fmt::Alignment::Center =>
178                        ((width - string_len) / 2, (width - string_len + 1) / 2),
179                };
180                // Avoid division by zero and optimize for common case.
181                if left > 0 {
182                    let c = f.fill();
183                    let chunk_len = encoder.put_filler(c, left);
184                    let padding = encoder.as_str();
185                    for _ in 0..(left / chunk_len) {
186                        f.write_str(padding)?;
187                    }
188                    f.write_str(&padding[..((left % chunk_len) * c.len_utf8())])?;
189                    encoder.clear();
190                }
191                right
192            } else {
193                0
194            }
195        } else {
196            0
197        };
198
199        match f.precision() {
200            Some(max) if self.bytes.len() > (max + 1) / 2 => {
201                write!(f, "{}", self.bytes[..(max / 2)].as_hex())?;
202                if max % 2 == 1 && self.bytes.len() > max / 2 + 1 {
203                    f.write_char(
204                        case.table().byte_to_hex(self.bytes[max / 2 + 1]).as_bytes()[1].into(),
205                    )?;
206                }
207            }
208            Some(_) | None => {
209                let mut chunks = self.bytes.chunks_exact(512);
210                for chunk in &mut chunks {
211                    encoder.put_bytes(chunk, case);
212                    f.write_str(encoder.as_str())?;
213                    encoder.clear();
214                }
215                encoder.put_bytes(chunks.remainder(), case);
216                f.write_str(encoder.as_str())?;
217            }
218        }
219
220        // Avoid division by zero and optimize for common case.
221        if pad_right > 0 {
222            encoder.clear();
223            let c = f.fill();
224            let chunk_len = encoder.put_filler(c, pad_right);
225            let padding = encoder.as_str();
226            for _ in 0..(pad_right / chunk_len) {
227                f.write_str(padding)?;
228            }
229            f.write_str(&padding[..((pad_right % chunk_len) * c.len_utf8())])?;
230        }
231        Ok(())
232    }
233}
234
235impl<'a> fmt::Display for DisplayByteSlice<'a> {
236    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
237}
238
239impl<'a> fmt::Debug for DisplayByteSlice<'a> {
240    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
241}
242
243impl<'a> fmt::LowerHex for DisplayByteSlice<'a> {
244    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
245}
246
247impl<'a> fmt::UpperHex for DisplayByteSlice<'a> {
248    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
249}
250
251/// Displays byte array as hex.
252///
253/// Created by [`<&[u8; CAP / 2] as DisplayHex>::as_hex`](DisplayHex::as_hex).
254pub struct DisplayArray<'a, const CAP: usize> {
255    array: &'a [u8],
256}
257
258impl<'a, const CAP: usize> DisplayArray<'a, CAP> {
259    /// Creates the wrapper.
260    ///
261    /// # Panics
262    ///
263    /// When the length of array is greater than capacity / 2.
264    #[inline]
265    fn new(array: &'a [u8]) -> Self {
266        assert!(array.len() <= CAP / 2);
267        DisplayArray { array }
268    }
269
270    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
271        let mut encoder = BufEncoder::<CAP>::new();
272        encoder.put_bytes(self.array, case);
273        f.pad_integral(true, "0x", encoder.as_str())
274    }
275}
276
277impl<'a, const LEN: usize> fmt::Display for DisplayArray<'a, LEN> {
278    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
279}
280
281impl<'a, const LEN: usize> fmt::Debug for DisplayArray<'a, LEN> {
282    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
283}
284
285impl<'a, const LEN: usize> fmt::LowerHex for DisplayArray<'a, LEN> {
286    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
287}
288
289impl<'a, const LEN: usize> fmt::UpperHex for DisplayArray<'a, LEN> {
290    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
291}
292
293macro_rules! impl_array_as_hex {
294    ($($len:expr),*) => {
295        $(
296            impl<'a> DisplayHex for &'a [u8; $len] {
297                type Display = DisplayArray<'a, {$len * 2}>;
298
299                fn as_hex(self) -> Self::Display {
300                    DisplayArray::new(self)
301                }
302            }
303        )*
304    }
305}
306
307impl_array_as_hex!(
308    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,
309    2048, 4096
310);
311
312/// Format known-length array as hex.
313///
314/// This supports all formatting options of formatter and may be faster than calling `as_hex()` on
315/// an arbitrary `&[u8]`. Note that the implementation intentionally keeps leading zeros even when
316/// not requested. This is designed to display values such as hashes and keys and removing leading
317/// zeros would be confusing.
318///
319/// Note that the bytes parameter is `IntoIterator` this means that if you would like to do some
320/// manipulation to the byte array before formatting then you can. For example `bytes.iter().rev()`
321/// to print the array backwards.
322///
323/// ## Parameters
324///
325/// * `$formatter` - a [`fmt::Formatter`].
326/// * `$len` known length of `$bytes`, must be a const expression.
327/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
328/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case.
329///
330/// ## Panics
331///
332/// This macro panics if `$len` is not equal to `$bytes.len()`. It also fails to compile if `$len`
333/// is more than half of `usize::MAX`.
334#[macro_export]
335macro_rules! fmt_hex_exact {
336    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
337        // statically check $len
338        #[allow(deprecated)]
339        const _: () = [()][($len > usize::MAX / 2) as usize];
340        assert_eq!($bytes.len(), $len);
341        $crate::display::fmt_hex_exact_fn::<_, { $len * 2 }>($formatter, $bytes, $case)
342    }};
343}
344pub use fmt_hex_exact;
345
346/// Adds `core::fmt` trait implementations to type `$ty`.
347///
348/// Implements:
349///
350/// - `fmt::{LowerHex, UpperHex}` using [`fmt_hex_exact`].
351/// - `fmt::{Display, Debug}` by calling `LowerHex`.
352///
353/// Requires:
354///
355/// - `$ty` must implement `IntoIterator<Item=Borrow<u8>>`.
356///
357/// ## Parameters
358///
359/// * `$ty` - the type to implement traits on.
360/// * `$len` - known length of `$bytes`, must be a const expression.
361/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
362/// * `$reverse` - true if you want the array to be displayed backwards.
363/// * `$gen: $gent` - optional generic type(s) and trait bound(s) to put on `$ty` e.g, `F: Foo`.
364///
365/// ## Examples
366///
367/// ```
368/// # use core::borrow::Borrow;
369/// # use hex_conservative::impl_fmt_traits;
370/// struct Wrapper([u8; 4]);
371///
372/// impl Borrow<[u8]> for Wrapper {
373///     fn borrow(&self) -> &[u8] { &self.0[..] }
374/// }
375///
376/// impl_fmt_traits! {
377///     impl fmt_traits for Wrapper {
378///         const LENGTH: usize = 4;
379///     }
380/// }
381///
382/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
383/// assert_eq!(format!("{}", w), "12345678");
384/// ```
385///
386/// We support generics on `$ty`:
387///
388/// ```
389/// # use core::borrow::Borrow;
390/// # use core::marker::PhantomData;
391/// # use hex_conservative::impl_fmt_traits;
392/// struct Wrapper<T>([u8; 4], PhantomData<T>);
393///
394/// // `Clone` is just some arbitrary trait.
395/// impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
396///     fn borrow(&self) -> &[u8] { &self.0[..] }
397/// }
398///
399/// impl_fmt_traits! {
400///     impl<T: Clone> fmt_traits for Wrapper<T> {
401///         const LENGTH: usize = 4;
402///     }
403/// }
404///
405/// let w = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
406/// assert_eq!(format!("{}", w), "12345678");
407/// ```
408///
409/// And also, as is required by `rust-bitcoin`, we support displaying
410/// the hex string byte-wise backwards:
411///
412/// ```
413/// # use core::borrow::Borrow;
414/// # use hex_conservative::impl_fmt_traits;
415/// struct Wrapper([u8; 4]);
416///
417/// impl Borrow<[u8]> for Wrapper {
418///     fn borrow(&self) -> &[u8] { &self.0[..] }
419/// }
420///
421/// impl_fmt_traits! {
422///     #[display_backward(true)]
423///     impl fmt_traits for Wrapper {
424///         const LENGTH: usize = 4;
425///     }
426/// }
427/// let w = Wrapper([0x12, 0x34, 0x56, 0x78]);
428/// assert_eq!(format!("{}", w), "78563412");
429/// ```
430#[macro_export]
431macro_rules! impl_fmt_traits {
432    // Without generic and trait bounds and without display_backward attribute.
433    (impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
434        $crate::impl_fmt_traits! {
435            #[display_backward(false)]
436            impl<> fmt_traits for $ty<> {
437                const LENGTH: usize = $len;
438            }
439        }
440    };
441    // Without generic and trait bounds and with display_backward attribute.
442    (#[display_backward($reverse:expr)] impl fmt_traits for $ty:ident { const LENGTH: usize = $len:expr; }) => {
443        $crate::impl_fmt_traits! {
444            #[display_backward($reverse)]
445            impl<> fmt_traits for $ty<> {
446                const LENGTH: usize = $len;
447            }
448        }
449    };
450    // With generic and trait bounds and without display_backward attribute.
451    (impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
452        $crate::impl_fmt_traits! {
453            #[display_backward(false)]
454            impl<$($gen: $gent),*> fmt_traits for $ty<$($unused),*> {
455                const LENGTH: usize = $len;
456            }
457        }
458    };
459    // With generic and trait bounds and display_backward attribute.
460    (#[display_backward($reverse:expr)] impl<$($gen:ident: $gent:ident),*> fmt_traits for $ty:ident<$($unused:ident),*> { const LENGTH: usize = $len:expr; }) => {
461        impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
462            #[inline]
463            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
464                let case = $crate::Case::Lower;
465
466                if $reverse {
467                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
468                    $crate::fmt_hex_exact!(f, $len, bytes, case)
469                } else {
470                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
471                    $crate::fmt_hex_exact!(f, $len, bytes, case)
472                }
473            }
474        }
475
476        impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> {
477            #[inline]
478            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
479                let case = $crate::Case::Upper;
480
481                if $reverse {
482                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
483                    $crate::fmt_hex_exact!(f, $len, bytes, case)
484                } else {
485                    let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
486                    $crate::fmt_hex_exact!(f, $len, bytes, case)
487                }
488            }
489        }
490
491        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> {
492            #[inline]
493            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
494                $crate::_export::_core::fmt::LowerHex::fmt(self, f)
495            }
496        }
497
498        impl<$($gen: $gent),*> $crate::_export::_core::fmt::Debug for $ty<$($gen),*> {
499            #[inline]
500            fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
501                $crate::_export::_core::fmt::LowerHex::fmt(&self, f)
502            }
503        }
504    };
505}
506pub use impl_fmt_traits;
507
508// Implementation detail of `write_hex_exact` macro to de-duplicate the code
509//
510// Whether hex is an integer or a string is debatable, we cater a little bit to each.
511// - We support users adding `0x` prefix using "{:#}" (treating hex like an integer).
512// - We support limiting the output using precision "{:.10}" (treating hex like a string).
513#[doc(hidden)]
514#[inline]
515pub fn fmt_hex_exact_fn<I, const N: usize>(
516    f: &mut fmt::Formatter,
517    bytes: I,
518    case: Case,
519) -> fmt::Result
520where
521    I: IntoIterator,
522    I::Item: Borrow<u8>,
523{
524    let mut encoder = BufEncoder::<N>::new();
525    encoder.put_bytes(bytes, case);
526    let encoded = encoder.as_str();
527
528    if let Some(precision) = f.precision() {
529        if encoded.len() > precision {
530            return f.pad_integral(true, "0x", &encoded[..precision]);
531        }
532    }
533    f.pad_integral(true, "0x", encoded)
534}
535
536#[cfg(test)]
537mod tests {
538    #[cfg(feature = "alloc")]
539    use super::*;
540
541    #[cfg(feature = "alloc")]
542    mod alloc {
543        use core::marker::PhantomData;
544
545        use super::*;
546
547        fn check_encoding(bytes: &[u8]) {
548            use core::fmt::Write;
549
550            let s1 = bytes.to_lower_hex_string();
551            let mut s2 = String::with_capacity(bytes.len() * 2);
552            for b in bytes {
553                write!(s2, "{:02x}", b).unwrap();
554            }
555            assert_eq!(s1, s2);
556        }
557
558        #[test]
559        fn empty() { check_encoding(b""); }
560
561        #[test]
562        fn single() { check_encoding(b"*"); }
563
564        #[test]
565        fn two() { check_encoding(b"*x"); }
566
567        #[test]
568        fn just_below_boundary() { check_encoding(&[42; 512]); }
569
570        #[test]
571        fn just_above_boundary() { check_encoding(&[42; 513]); }
572
573        #[test]
574        fn just_above_double_boundary() { check_encoding(&[42; 1025]); }
575
576        #[test]
577        fn fmt_exact_macro() {
578            use crate::alloc::string::ToString;
579
580            struct Dummy([u8; 32]);
581
582            impl fmt::Display for Dummy {
583                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
584                    fmt_hex_exact!(f, 32, &self.0, Case::Lower)
585                }
586            }
587            let dummy = Dummy([42; 32]);
588            assert_eq!(dummy.to_string(), "2a".repeat(32));
589            assert_eq!(format!("{:.10}", dummy), "2a".repeat(5));
590        }
591
592        #[test]
593        fn display_short_with_padding() {
594            let v = vec![0xbe, 0xef];
595            assert_eq!(format!("Hello {:<8}!", v.as_hex()), "Hello beef    !");
596            assert_eq!(format!("Hello {:-<8}!", v.as_hex()), "Hello beef----!");
597            assert_eq!(format!("Hello {:^8}!", v.as_hex()), "Hello   beef  !");
598            assert_eq!(format!("Hello {:>8}!", v.as_hex()), "Hello     beef!");
599        }
600
601        #[test]
602        fn display_long() {
603            // Note this string is shorter than the one above.
604            let v = vec![0xab; 512];
605            let mut want = "0".repeat(2000 - 1024);
606            want.extend(core::iter::repeat("ab").take(512));
607            let got = format!("{:0>2000}", v.as_hex());
608            assert_eq!(got, want)
609        }
610
611        // Precision and padding act the same as for strings in the stdlib (because we use `Formatter::pad`).
612
613        #[test]
614        fn precision_truncates() {
615            // Precision gets the most significant bytes.
616            let v = vec![0x12, 0x34, 0x56, 0x78];
617            // Remember the integer is number of hex chars not number of bytes.
618            assert_eq!(format!("{0:.4}", v.as_hex()), "1234");
619        }
620
621        #[test]
622        fn precision_with_padding_truncates() {
623            // Precision gets the most significant bytes.
624            let v = vec![0x12, 0x34, 0x56, 0x78];
625            assert_eq!(format!("{0:10.4}", v.as_hex()), "1234      ");
626        }
627
628        #[test]
629        fn precision_with_padding_pads_right() {
630            let v = vec![0x12, 0x34, 0x56, 0x78];
631            assert_eq!(format!("{0:10.20}", v.as_hex()), "12345678  ");
632        }
633
634        #[test]
635        fn precision_with_padding_pads_left() {
636            let v = vec![0x12, 0x34, 0x56, 0x78];
637            assert_eq!(format!("{0:>10.20}", v.as_hex()), "  12345678");
638        }
639
640        #[test]
641        fn precision_with_padding_pads_center() {
642            let v = vec![0x12, 0x34, 0x56, 0x78];
643            assert_eq!(format!("{0:^10.20}", v.as_hex()), " 12345678 ");
644        }
645
646        #[test]
647        fn precision_with_padding_pads_center_odd() {
648            let v = vec![0x12, 0x34, 0x56, 0x78];
649            assert_eq!(format!("{0:^11.20}", v.as_hex()), " 12345678  ");
650        }
651
652        #[test]
653        fn precision_does_not_extend() {
654            let v = vec![0x12, 0x34, 0x56, 0x78];
655            assert_eq!(format!("{0:.16}", v.as_hex()), "12345678");
656        }
657
658        #[test]
659        fn padding_extends() {
660            let v = vec![0xab; 2];
661            assert_eq!(format!("{:0>8}", v.as_hex()), "0000abab");
662        }
663
664        #[test]
665        fn padding_does_not_truncate() {
666            let v = vec![0x12, 0x34, 0x56, 0x78];
667            assert_eq!(format!("{:0>4}", v.as_hex()), "12345678");
668        }
669
670        #[test]
671        fn hex_fmt_impl_macro_forward() {
672            struct Wrapper([u8; 4]);
673
674            impl Borrow<[u8]> for Wrapper {
675                fn borrow(&self) -> &[u8] { &self.0[..] }
676            }
677
678            impl_fmt_traits! {
679                #[display_backward(false)]
680                impl fmt_traits for Wrapper {
681                    const LENGTH: usize = 4;
682                }
683            }
684
685            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
686
687            let want = "12345678";
688            let got = format!("{}", tc);
689            assert_eq!(got, want);
690        }
691
692        #[test]
693        fn hex_fmt_impl_macro_backwards() {
694            struct Wrapper([u8; 4]);
695
696            impl Borrow<[u8]> for Wrapper {
697                fn borrow(&self) -> &[u8] { &self.0[..] }
698            }
699
700            impl_fmt_traits! {
701                #[display_backward(true)]
702                impl fmt_traits for Wrapper {
703                    const LENGTH: usize = 4;
704                }
705            }
706
707            let tc = Wrapper([0x12, 0x34, 0x56, 0x78]);
708
709            let want = "78563412";
710            let got = format!("{}", tc);
711            assert_eq!(got, want);
712        }
713
714        #[test]
715        fn hex_fmt_impl_macro_gen_forward() {
716            struct Wrapper<T>([u8; 4], PhantomData<T>);
717
718            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
719                fn borrow(&self) -> &[u8] { &self.0[..] }
720            }
721
722            impl_fmt_traits! {
723                #[display_backward(false)]
724                impl<T: Clone> fmt_traits for Wrapper<T> {
725                    const LENGTH: usize = 4;
726                }
727            }
728
729            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
730            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
731
732            let want = "12345678";
733            let got = format!("{}", tc);
734            assert_eq!(got, want);
735        }
736
737        #[test]
738        fn hex_fmt_impl_macro_gen_backwards() {
739            struct Wrapper<T>([u8; 4], PhantomData<T>);
740
741            impl<T: Clone> Borrow<[u8]> for Wrapper<T> {
742                fn borrow(&self) -> &[u8] { &self.0[..] }
743            }
744
745            impl_fmt_traits! {
746                #[display_backward(true)]
747                impl<T: Clone> fmt_traits for Wrapper<T> {
748                    const LENGTH: usize = 4;
749                }
750            }
751
752            // We just use `u32` here as some arbitrary type that implements some arbitrary trait.
753            let tc = Wrapper([0x12, 0x34, 0x56, 0x78], PhantomData::<u32>);
754
755            let want = "78563412";
756            let got = format!("{}", tc);
757            assert_eq!(got, want);
758        }
759    }
760}