Skip to main content

hex_conservative/
error.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! The error types.
4//!
5//! These types are returned when hex decoding fails. The high-level ones are
6//! [`DecodeFixedLengthBytesError`] and [`DecodeVariableLengthBytesError`] which represent all
7//! possible ways in which hex decoding may fail in the two most common decoding scenarios.
8
9use core::convert::Infallible;
10use core::fmt;
11#[cfg(feature = "std")]
12use std::error::Error as StdError;
13#[cfg(all(not(feature = "std"), feature = "newer-rust-version"))]
14if_rust_version::if_rust_version! {
15    >= 1.81 {
16        use core::error::Error as StdError;
17    }
18}
19
20#[cfg(feature = "std")]
21macro_rules! if_std_error {
22    ({ $($if_yes:tt)* } $(else { $($if_not:tt)* })?) => {
23        #[cfg_attr(docsrs, doc(cfg(any(feature = "std", all(feature = "newer-rust-version", rust_version = ">= 1.81.0")))))]
24        $($if_yes)*
25    }
26}
27
28#[cfg(all(not(feature = "std"), feature = "newer-rust-version"))]
29macro_rules! if_std_error {
30    ({ $($if_yes:tt)* } $(else { $($if_not:tt)* })?) => {
31        if_rust_version::if_rust_version! {
32            >= 1.81 {
33                #[cfg_attr(docsrs, doc(cfg(any(feature = "std", all(feature = "newer-rust-version", rust_version = ">= 1.81.0")))))]
34                $($if_yes)*
35            } $(else { $($if_not)* })?
36        }
37    }
38}
39
40#[cfg(all(not(feature = "std"), not(feature = "newer-rust-version")))]
41macro_rules! if_std_error {
42    ({ $($if_yes:tt)* } $(else { $($if_not:tt)* })?) => {
43        $($($if_not)*)?
44    }
45}
46
47/// Formats error.
48///
49/// If `std` feature is OFF appends error source (delimited by `: `). We do this because
50/// `e.source()` is only available in std builds, without this macro the error source is lost for
51/// no-std builds.
52macro_rules! write_err {
53    ($writer:expr, $string:literal $(, $args:expr)*; $source:expr) => {
54        {
55            if_std_error! {
56                {
57                    {
58                        let _ = &$source;   // Prevents clippy warnings.
59                        write!($writer, $string $(, $args)*)
60                    }
61                } else {
62                    {
63                        write!($writer, concat!($string, ": {}") $(, $args)*, $source)
64                    }
65                }
66            }
67        }
68    }
69}
70
71/// Error returned when hex decoding a hex string with variable length.
72///
73/// This represents the first error encountered during decoding, however we may add other remaining
74/// ones in the future.
75///
76/// This error differs from [`DecodeFixedLengthBytesError`] in that the number of bytes is only known
77/// at run time - e.g. when decoding `Vec<u8>`.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum DecodeVariableLengthBytesError {
80    /// Non-hexadecimal character.
81    InvalidChar(InvalidCharError),
82    /// Purported hex string had odd (not even) length.
83    OddLengthString(OddLengthStringError),
84}
85
86impl DecodeVariableLengthBytesError {
87    /// Adds `by_bytes` to all character positions stored inside.
88    ///
89    /// If you're parsing a larger string that consists of multiple hex sub-strings and want to
90    /// return `InvalidCharError` you may need to use this function so that the callers of your
91    /// parsing function can tell the exact position where decoding failed relative to the start of
92    /// the string passed into your parsing function.
93    ///
94    /// Note that this function has the standard Rust overflow behavior because you should only
95    /// ever pass in the position of the parsed hex string relative to the start of the entire
96    /// input. In that case overflow is impossible.
97    ///
98    /// This method consumes and returns `self` so that calling it inside a closure passed into
99    /// [`Result::map_err`] is convenient.
100    #[must_use]
101    #[inline]
102    pub fn offset(self, by_bytes: usize) -> Self {
103        use DecodeVariableLengthBytesError as E;
104
105        match self {
106            E::InvalidChar(e) => E::InvalidChar(e.offset(by_bytes)),
107            E::OddLengthString(e) => E::OddLengthString(e),
108        }
109    }
110}
111
112impl From<Infallible> for DecodeVariableLengthBytesError {
113    #[inline]
114    fn from(never: Infallible) -> Self { match never {} }
115}
116
117impl fmt::Display for DecodeVariableLengthBytesError {
118    #[inline]
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        use DecodeVariableLengthBytesError as E;
121
122        match *self {
123            E::InvalidChar(ref e) => write_err!(f, "failed to decode hex"; e),
124            E::OddLengthString(ref e) => write_err!(f, "failed to decode hex"; e),
125        }
126    }
127}
128
129if_std_error! {{
130    impl StdError for DecodeVariableLengthBytesError {
131        #[inline]
132        fn source(&self) -> Option<&(dyn StdError + 'static)> {
133            use DecodeVariableLengthBytesError as E;
134
135            match *self {
136                E::InvalidChar(ref e) => Some(e),
137                E::OddLengthString(ref e) => Some(e),
138            }
139        }
140    }
141}}
142
143impl From<InvalidCharError> for DecodeVariableLengthBytesError {
144    #[inline]
145    fn from(e: InvalidCharError) -> Self { Self::InvalidChar(e) }
146}
147
148impl From<OddLengthStringError> for DecodeVariableLengthBytesError {
149    #[inline]
150    fn from(e: OddLengthStringError) -> Self { Self::OddLengthString(e) }
151}
152
153/// Invalid hex character.
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct InvalidCharError {
156    pub(crate) invalid: u8,
157    pub(crate) pos: usize,
158}
159
160impl InvalidCharError {
161    /// Returns the invalid character byte.
162    #[inline]
163    pub(crate) fn invalid_char(&self) -> u8 { self.invalid }
164    /// Returns the position of the invalid character byte.
165    #[inline]
166    pub fn pos(&self) -> usize { self.pos }
167
168    /// Adds `by_bytes` to all character positions stored inside.
169    ///
170    /// **Important**: if you have `DecodeVariableLengthBytesError` or `DecodeFixedLengthBytesError` you
171    /// should call the method *on them* - do not match them and manually call this method. Doing
172    /// so may lead to broken behavior in the future.
173    ///
174    /// If you're parsing a larger string that consists of multiple hex sub-strings and want to
175    /// return `InvalidCharError` you may need to use this function so that the callers of your
176    /// parsing function can tell the exact position where decoding failed relative to the start of
177    /// the string passed into your parsing function.
178    ///
179    /// Note that this function has the standard Rust overflow behavior because you should only
180    /// ever pass in the position of the parsed hex string relative to the start of the entire
181    /// input. In that case overflow is impossible.
182    ///
183    /// This method consumes and returns `self` so that calling it inside a closure passed into
184    /// [`Result::map_err`] is convenient.
185    #[must_use]
186    #[inline]
187    pub fn offset(mut self, by_bytes: usize) -> Self {
188        self.pos += by_bytes;
189        self
190    }
191}
192
193impl From<Infallible> for InvalidCharError {
194    #[inline]
195    fn from(never: Infallible) -> Self { match never {} }
196}
197
198/// Note that the implementation displays position as 1-based instead of 0-based to be more
199/// suitable to end users who might be non-programmers.
200impl fmt::Display for InvalidCharError {
201    #[inline]
202    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203        // We're displaying this for general audience, not programmers, so we want to do 1-based
204        // position but that might confuse programmers who might think it's 0-based. Hopefully
205        // using more wordy approach will avoid the confusion.
206
207        // format_args! would be simpler but we can't use it because of  Rust issue #92698.
208        struct Format<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result>(F);
209        impl<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Display for Format<F> {
210            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0(f) }
211        }
212
213        // The lifetime is not extended in MSRV, so we need this.
214        let which;
215        let which: &dyn fmt::Display = match self.pos() {
216            0 => &"1st",
217            1 => &"2nd",
218            2 => &"3rd",
219            pos => {
220                which = Format(move |f| write!(f, "{}th", pos + 1));
221                &which
222            }
223        };
224
225        // The lifetime is not extended in MSRV, so we need these.
226        let chr_ascii;
227        let chr_non_ascii;
228
229        let invalid_char = self.invalid_char();
230        // We're currently not storing the entire character, so we need to make sure values >=
231        // 128 don't get misinterpreted as ISO-8859-1.
232        let chr: &dyn fmt::Display = if self.invalid_char().is_ascii() {
233            // Yes, the Debug output is correct here. Display would print the characters
234            // directly which would be confusing in case of control characters and it would
235            // also mess up the formatting. The `Debug` implementation of `char` properly
236            // escapes such characters.
237            chr_ascii = Format(move |f| write!(f, "{:?}", invalid_char as char));
238            &chr_ascii
239        } else {
240            chr_non_ascii = Format(move |f| write!(f, "{:#02x}", invalid_char));
241            &chr_non_ascii
242        };
243
244        write!(f, "the {} character, {}, is not a valid hex digit", which, chr)
245    }
246}
247
248if_std_error! {{
249    impl StdError for InvalidCharError {
250        #[inline]
251        fn source(&self) -> Option<&(dyn StdError + 'static)> { None }
252    }
253}}
254
255/// Purported hex string had odd length.
256#[derive(Debug, Clone, PartialEq, Eq)]
257pub struct OddLengthStringError {
258    pub(crate) len: usize,
259}
260
261impl OddLengthStringError {
262    /// Returns the odd length of the input string.
263    #[inline]
264    pub fn length(&self) -> usize { self.len }
265}
266
267impl From<Infallible> for OddLengthStringError {
268    #[inline]
269    fn from(never: Infallible) -> Self { match never {} }
270}
271
272impl fmt::Display for OddLengthStringError {
273    #[inline]
274    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275        if self.length() == 1 {
276            write!(f, "the hex string is 1 byte long which is not an even number")
277        } else {
278            write!(f, "the hex string is {} bytes long which is not an even number", self.length())
279        }
280    }
281}
282
283if_std_error! {{
284    impl StdError for OddLengthStringError {
285        #[inline]
286        fn source(&self) -> Option<&(dyn StdError + 'static)> { None }
287    }
288}}
289
290/// Error returned when hex decoding bytes whose length is known at compile time.
291///
292/// This error differs from [`DecodeVariableLengthBytesError`] in that the number of bytes is known at
293/// compile time - e.g. when decoding to an array of bytes.
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub enum DecodeFixedLengthBytesError {
296    /// Non-hexadecimal character.
297    InvalidChar(InvalidCharError),
298    /// Tried to parse fixed-length hash from a string with the wrong length.
299    InvalidLength(InvalidLengthError),
300}
301
302impl DecodeFixedLengthBytesError {
303    /// Adds `by_bytes` to all character positions stored inside.
304    ///
305    /// If you're parsing a larger string that consists of multiple hex sub-strings and want to
306    /// return `InvalidCharError` you may need to use this function so that the callers of your
307    /// parsing function can tell the exact position where decoding failed relative to the start of
308    /// the string passed into your parsing function.
309    ///
310    /// Note that this function has the standard Rust overflow behavior because you should only
311    /// ever pass in the position of the parsed hex string relative to the start of the entire
312    /// input. In that case overflow is impossible.
313    ///
314    /// This method consumes and returns `self` so that calling it inside a closure passed into
315    /// [`Result::map_err`] is convenient.
316    #[must_use]
317    #[inline]
318    pub fn offset(self, by_bytes: usize) -> Self {
319        use DecodeFixedLengthBytesError as E;
320
321        match self {
322            E::InvalidChar(e) => E::InvalidChar(e.offset(by_bytes)),
323            E::InvalidLength(e) => E::InvalidLength(e),
324        }
325    }
326}
327
328impl From<Infallible> for DecodeFixedLengthBytesError {
329    #[inline]
330    fn from(never: Infallible) -> Self { match never {} }
331}
332
333impl fmt::Display for DecodeFixedLengthBytesError {
334    #[inline]
335    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
336        use DecodeFixedLengthBytesError as E;
337
338        match *self {
339            E::InvalidChar(ref e) => write_err!(f, "failed to parse hex"; e),
340            E::InvalidLength(ref e) => write_err!(f, "failed to parse hex"; e),
341        }
342    }
343}
344
345if_std_error! {{
346    impl StdError for DecodeFixedLengthBytesError {
347        #[inline]
348        fn source(&self) -> Option<&(dyn StdError + 'static)> {
349            use DecodeFixedLengthBytesError as E;
350
351            match *self {
352                E::InvalidChar(ref e) => Some(e),
353                E::InvalidLength(ref e) => Some(e),
354            }
355        }
356    }
357}}
358
359impl From<InvalidCharError> for DecodeFixedLengthBytesError {
360    #[inline]
361    fn from(e: InvalidCharError) -> Self { Self::InvalidChar(e) }
362}
363
364impl From<InvalidLengthError> for DecodeFixedLengthBytesError {
365    #[inline]
366    fn from(e: InvalidLengthError) -> Self { Self::InvalidLength(e) }
367}
368
369/// Tried to parse fixed-length hash from a string with the wrong length.
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct InvalidLengthError {
372    /// The expected length.
373    pub(crate) expected: usize,
374    /// The invalid length.
375    pub(crate) invalid: usize,
376}
377
378impl InvalidLengthError {
379    /// Returns the expected length.
380    ///
381    /// Note that this represents both the number of bytes and the number of characters that needs
382    /// to be passed into the decoder, since the hex digits are ASCII and thus always 1-byte long.
383    #[inline]
384    pub fn expected_length(&self) -> usize { self.expected }
385
386    /// Returns the number of *hex bytes* passed to the hex decoder.
387    ///
388    /// Note that this does not imply the number of characters nor hex digits since they may be
389    /// invalid (wide unicode chars).
390    #[inline]
391    pub fn invalid_length(&self) -> usize { self.invalid }
392}
393
394impl From<Infallible> for InvalidLengthError {
395    #[inline]
396    fn from(never: Infallible) -> Self { match never {} }
397}
398
399impl fmt::Display for InvalidLengthError {
400    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
401        write!(
402            f,
403            // Note on singular vs plural: expected length is never odd, so it cannot be 1
404            "the hex string is {} bytes long but exactly {} bytes were required",
405            self.invalid_length(),
406            self.expected_length()
407        )
408    }
409}
410
411if_std_error! {{
412    impl StdError for InvalidLengthError {
413        #[inline]
414        fn source(&self) -> Option<&(dyn StdError + 'static)> { None }
415    }
416}}
417
418#[cfg(test)]
419#[cfg(feature = "std")]
420mod tests {
421    use super::*;
422    #[cfg(feature = "alloc")]
423    use crate::decode_to_vec;
424    use crate::error::{InvalidCharError, OddLengthStringError};
425    use crate::{decode_to_array, HexToBytesIter, InvalidLengthError};
426
427    fn check_source<T: std::error::Error>(error: &T) {
428        assert!(error.source().is_some());
429    }
430
431    #[cfg(feature = "alloc")]
432    #[test]
433    fn invalid_char_error() {
434        let result = decode_to_vec("12G4");
435        let error = result.unwrap_err();
436        if let DecodeVariableLengthBytesError::InvalidChar(e) = error {
437            assert!(!format!("{}", e).is_empty());
438            assert_eq!(e.invalid_char(), b'G');
439            assert_eq!(e.pos(), 2);
440        } else {
441            panic!("Expected InvalidCharError");
442        }
443    }
444
445    #[cfg(feature = "alloc")]
446    #[test]
447    fn odd_length_string_error() {
448        let result = decode_to_vec("123");
449        let error = result.unwrap_err();
450        assert!(!format!("{}", error).is_empty());
451        check_source(&error);
452        if let DecodeVariableLengthBytesError::OddLengthString(e) = error {
453            assert!(!format!("{}", e).is_empty());
454            assert_eq!(e.length(), 3);
455        } else {
456            panic!("Expected OddLengthStringError");
457        }
458    }
459
460    #[test]
461    fn invalid_length_error() {
462        let result = decode_to_array::<4>("123");
463        let error = result.unwrap_err();
464        assert!(!format!("{}", error).is_empty());
465        check_source(&error);
466        if let DecodeFixedLengthBytesError::InvalidLength(e) = error {
467            assert!(!format!("{}", e).is_empty());
468            assert_eq!(e.expected_length(), 8);
469            assert_eq!(e.invalid_length(), 3);
470        } else {
471            panic!("Expected InvalidLengthError");
472        }
473    }
474
475    #[test]
476    fn to_bytes_error() {
477        let error =
478            DecodeVariableLengthBytesError::OddLengthString(OddLengthStringError { len: 7 });
479        assert!(!format!("{}", error).is_empty());
480        check_source(&error);
481    }
482
483    #[test]
484    fn to_array_error() {
485        let error = DecodeFixedLengthBytesError::InvalidLength(InvalidLengthError {
486            expected: 8,
487            invalid: 7,
488        });
489        assert!(!format!("{}", error).is_empty());
490        check_source(&error);
491    }
492
493    #[test]
494    #[cfg(feature = "alloc")]
495    fn hex_error() {
496        let oddlen = "0123456789abcdef0";
497        let badchar1 = "Z123456789abcdef";
498        let badchar2 = "012Y456789abcdeb";
499        let badchar3 = "«23456789abcdef";
500
501        assert_eq!(decode_to_vec(oddlen).unwrap_err(), OddLengthStringError { len: 17 }.into());
502        assert_eq!(
503            decode_to_array::<4>(oddlen).unwrap_err(),
504            InvalidLengthError { invalid: 17, expected: 8 }.into()
505        );
506        assert_eq!(
507            decode_to_vec(badchar1).unwrap_err(),
508            InvalidCharError { pos: 0, invalid: b'Z' }.into()
509        );
510        assert_eq!(
511            decode_to_vec(badchar2).unwrap_err(),
512            InvalidCharError { pos: 3, invalid: b'Y' }.into()
513        );
514        assert_eq!(
515            decode_to_vec(badchar3).unwrap_err(),
516            InvalidCharError { pos: 0, invalid: 194 }.into()
517        );
518    }
519
520    #[test]
521    fn hex_error_position() {
522        let badpos1 = "Z123456789abcdef";
523        let badpos2 = "012Y456789abcdeb";
524        let badpos3 = "0123456789abcdeZ";
525        let badpos4 = "0123456789abYdef";
526
527        assert_eq!(
528            HexToBytesIter::new(badpos1).unwrap().next().unwrap().unwrap_err(),
529            InvalidCharError { pos: 0, invalid: b'Z' }
530        );
531        assert_eq!(
532            HexToBytesIter::new(badpos2).unwrap().nth(1).unwrap().unwrap_err(),
533            InvalidCharError { pos: 3, invalid: b'Y' }
534        );
535        assert_eq!(
536            HexToBytesIter::new(badpos3).unwrap().next_back().unwrap().unwrap_err(),
537            InvalidCharError { pos: 15, invalid: b'Z' }
538        );
539        assert_eq!(
540            HexToBytesIter::new(badpos4).unwrap().nth_back(1).unwrap().unwrap_err(),
541            InvalidCharError { pos: 12, invalid: b'Y' }
542        );
543    }
544
545    #[test]
546    fn hex_to_array() {
547        let len_sixteen = "0123456789abcdef";
548        assert!(decode_to_array::<8>(len_sixteen).is_ok());
549    }
550
551    #[test]
552    fn hex_to_array_error() {
553        let len_sixteen = "0123456789abcdef";
554        assert_eq!(
555            decode_to_array::<4>(len_sixteen).unwrap_err(),
556            InvalidLengthError { invalid: 16, expected: 8 }.into()
557        );
558    }
559
560    #[test]
561    #[cfg(feature = "alloc")]
562    fn mixed_case() {
563        use crate::display::DisplayHex as _;
564
565        let s = "DEADbeef0123";
566        let want_lower = "deadbeef0123";
567        let want_upper = "DEADBEEF0123";
568
569        let v = decode_to_vec(s).expect("valid hex");
570        assert_eq!(format!("{:x}", v.as_hex()), want_lower);
571        assert_eq!(format!("{:X}", v.as_hex()), want_upper);
572    }
573}