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}
70pub(crate) use write_err;
71
72/// Error returned when hex decoding a hex string with variable length.
73///
74/// This represents the first error encountered during decoding, however we may add other remaining
75/// ones in the future.
76///
77/// This error differs from [`DecodeFixedLengthBytesError`] in that the number of bytes is only known
78/// at run time - e.g. when decoding `Vec<u8>`.
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum DecodeVariableLengthBytesError {
81    /// Non-hexadecimal character.
82    InvalidChar(InvalidCharError),
83    /// Purported hex string had odd (not even) length.
84    OddLengthString(OddLengthStringError),
85}
86
87impl DecodeVariableLengthBytesError {
88    /// Adds `by_bytes` to all character positions stored inside.
89    ///
90    /// If you're parsing a larger string that consists of multiple hex sub-strings and want to
91    /// return `InvalidCharError` you may need to use this function so that the callers of your
92    /// parsing function can tell the exact position where decoding failed relative to the start of
93    /// the string passed into your parsing function.
94    ///
95    /// Note that this function has the standard Rust overflow behavior because you should only
96    /// ever pass in the position of the parsed hex string relative to the start of the entire
97    /// input. In that case overflow is impossible.
98    ///
99    /// This method consumes and returns `self` so that calling it inside a closure passed into
100    /// [`Result::map_err`] is convenient.
101    #[must_use]
102    #[inline]
103    pub fn offset(self, by_bytes: usize) -> Self {
104        use DecodeVariableLengthBytesError as E;
105
106        match self {
107            E::InvalidChar(e) => E::InvalidChar(e.offset(by_bytes)),
108            E::OddLengthString(e) => E::OddLengthString(e),
109        }
110    }
111}
112
113impl From<Infallible> for DecodeVariableLengthBytesError {
114    #[inline]
115    fn from(never: Infallible) -> Self { match never {} }
116}
117
118impl fmt::Display for DecodeVariableLengthBytesError {
119    #[inline]
120    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121        use DecodeVariableLengthBytesError as E;
122
123        match *self {
124            E::InvalidChar(ref e) => write_err!(f, "failed to decode hex"; e),
125            E::OddLengthString(ref e) => write_err!(f, "failed to decode hex"; e),
126        }
127    }
128}
129
130if_std_error! {{
131    impl StdError for DecodeVariableLengthBytesError {
132        #[inline]
133        fn source(&self) -> Option<&(dyn StdError + 'static)> {
134            use DecodeVariableLengthBytesError as E;
135
136            match *self {
137                E::InvalidChar(ref e) => Some(e),
138                E::OddLengthString(ref e) => Some(e),
139            }
140        }
141    }
142}}
143
144impl From<InvalidCharError> for DecodeVariableLengthBytesError {
145    #[inline]
146    fn from(e: InvalidCharError) -> Self { Self::InvalidChar(e) }
147}
148
149impl From<OddLengthStringError> for DecodeVariableLengthBytesError {
150    #[inline]
151    fn from(e: OddLengthStringError) -> Self { Self::OddLengthString(e) }
152}
153
154/// Invalid hex character.
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct InvalidCharError {
157    pub(crate) invalid: u8,
158    pub(crate) pos: usize,
159}
160
161impl From<Infallible> for InvalidCharError {
162    #[inline]
163    fn from(never: Infallible) -> Self { match never {} }
164}
165
166impl InvalidCharError {
167    /// Returns the invalid character byte.
168    #[inline]
169    pub(crate) fn invalid_char(&self) -> u8 { self.invalid }
170    /// Returns the position of the invalid character byte.
171    #[inline]
172    pub fn pos(&self) -> usize { self.pos }
173
174    /// Adds `by_bytes` to all character positions stored inside.
175    ///
176    /// **Important**: if you have `DecodeVariableLengthBytesError` or `DecodeFixedLengthBytesError` you
177    /// should call the method *on them* - do not match them and manually call this method. Doing
178    /// so may lead to broken behavior in the future.
179    ///
180    /// If you're parsing a larger string that consists of multiple hex sub-strings and want to
181    /// return `InvalidCharError` you may need to use this function so that the callers of your
182    /// parsing function can tell the exact position where decoding failed relative to the start of
183    /// the string passed into your parsing function.
184    ///
185    /// Note that this function has the standard Rust overflow behavior because you should only
186    /// ever pass in the position of the parsed hex string relative to the start of the entire
187    /// input. In that case overflow is impossible.
188    ///
189    /// This method consumes and returns `self` so that calling it inside a closure passed into
190    /// [`Result::map_err`] is convenient.
191    #[must_use]
192    #[inline]
193    pub fn offset(mut self, by_bytes: usize) -> Self {
194        self.pos += by_bytes;
195        self
196    }
197}
198
199/// Note that the implementation displays position as 1-based instead of 0-based to be more
200/// suitable to end users who might be non-programmers.
201impl fmt::Display for InvalidCharError {
202    #[inline]
203    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
204        // We're displaying this for general audience, not programmers, so we want to do 1-based
205        // position but that might confuse programmers who might think it's 0-based. Hopefully
206        // using more wordy approach will avoid the confusion.
207
208        // format_args! would be simpler but we can't use it because of  Rust issue #92698.
209        struct Format<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result>(F);
210        impl<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Display for Format<F> {
211            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0(f) }
212        }
213
214        // The lifetime is not extended in MSRV, so we need this.
215        let which;
216        let which: &dyn fmt::Display = match self.pos() {
217            0 => &"1st",
218            1 => &"2nd",
219            2 => &"3rd",
220            pos => {
221                which = Format(move |f| write!(f, "{}th", pos + 1));
222                &which
223            }
224        };
225
226        // The lifetime is not extended in MSRV, so we need these.
227        let chr_ascii;
228        let chr_non_ascii;
229
230        let invalid_char = self.invalid_char();
231        // We're currently not storing the entire character, so we need to make sure values >=
232        // 128 don't get misinterpreted as ISO-8859-1.
233        let chr: &dyn fmt::Display = if self.invalid_char().is_ascii() {
234            // Yes, the Debug output is correct here. Display would print the characters
235            // directly which would be confusing in case of control characters and it would
236            // also mess up the formatting. The `Debug` implementation of `char` properly
237            // escapes such characters.
238            chr_ascii = Format(move |f| write!(f, "{:?}", invalid_char as char));
239            &chr_ascii
240        } else {
241            chr_non_ascii = Format(move |f| write!(f, "{:#02x}", invalid_char));
242            &chr_non_ascii
243        };
244
245        write!(f, "the {} character, {}, is not a valid hex digit", which, chr)
246    }
247}
248
249if_std_error! {{
250    impl StdError for InvalidCharError {}
251}}
252
253/// Purported hex string had odd length.
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct OddLengthStringError {
256    pub(crate) len: usize,
257}
258
259impl From<Infallible> for OddLengthStringError {
260    #[inline]
261    fn from(never: Infallible) -> Self { match never {} }
262}
263
264impl OddLengthStringError {
265    /// Returns the odd length of the input string.
266    #[inline]
267    pub fn length(&self) -> usize { self.len }
268}
269
270impl fmt::Display for OddLengthStringError {
271    #[inline]
272    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
273        if self.length() == 1 {
274            write!(f, "the hex string is 1 byte long which is not an even number")
275        } else {
276            write!(f, "the hex string is {} bytes long which is not an even number", self.length())
277        }
278    }
279}
280
281if_std_error! {{
282    impl StdError for OddLengthStringError {}
283}}
284
285/// Error returned when hex decoding bytes whose length is known at compile time.
286///
287/// This error differs from [`DecodeVariableLengthBytesError`] in that the number of bytes is known at
288/// compile time - e.g. when decoding to an array of bytes.
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub enum DecodeFixedLengthBytesError {
291    /// Non-hexadecimal character.
292    InvalidChar(InvalidCharError),
293    /// Tried to parse fixed-length hash from a string with the wrong length.
294    InvalidLength(InvalidLengthError),
295}
296
297impl DecodeFixedLengthBytesError {
298    /// Adds `by_bytes` to all character positions stored inside.
299    ///
300    /// If you're parsing a larger string that consists of multiple hex sub-strings and want to
301    /// return `InvalidCharError` you may need to use this function so that the callers of your
302    /// parsing function can tell the exact position where decoding failed relative to the start of
303    /// the string passed into your parsing function.
304    ///
305    /// Note that this function has the standard Rust overflow behavior because you should only
306    /// ever pass in the position of the parsed hex string relative to the start of the entire
307    /// input. In that case overflow is impossible.
308    ///
309    /// This method consumes and returns `self` so that calling it inside a closure passed into
310    /// [`Result::map_err`] is convenient.
311    #[must_use]
312    #[inline]
313    pub fn offset(self, by_bytes: usize) -> Self {
314        use DecodeFixedLengthBytesError as E;
315
316        match self {
317            E::InvalidChar(e) => E::InvalidChar(e.offset(by_bytes)),
318            E::InvalidLength(e) => E::InvalidLength(e),
319        }
320    }
321}
322
323impl From<Infallible> for DecodeFixedLengthBytesError {
324    #[inline]
325    fn from(never: Infallible) -> Self { match never {} }
326}
327
328impl fmt::Display for DecodeFixedLengthBytesError {
329    #[inline]
330    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331        use DecodeFixedLengthBytesError as E;
332
333        match *self {
334            E::InvalidChar(ref e) => write_err!(f, "failed to parse hex"; e),
335            E::InvalidLength(ref e) => write_err!(f, "failed to parse hex"; e),
336        }
337    }
338}
339
340if_std_error! {{
341    impl StdError for DecodeFixedLengthBytesError {
342        #[inline]
343        fn source(&self) -> Option<&(dyn StdError + 'static)> {
344            use DecodeFixedLengthBytesError as E;
345
346            match *self {
347                E::InvalidChar(ref e) => Some(e),
348                E::InvalidLength(ref e) => Some(e),
349            }
350        }
351    }
352}}
353
354impl From<InvalidCharError> for DecodeFixedLengthBytesError {
355    #[inline]
356    fn from(e: InvalidCharError) -> Self { Self::InvalidChar(e) }
357}
358
359impl From<InvalidLengthError> for DecodeFixedLengthBytesError {
360    #[inline]
361    fn from(e: InvalidLengthError) -> Self { Self::InvalidLength(e) }
362}
363
364/// Tried to parse fixed-length hash from a string with the wrong length.
365#[derive(Debug, Clone, PartialEq, Eq)]
366pub struct InvalidLengthError {
367    /// The expected length.
368    pub(crate) expected: usize,
369    /// The invalid length.
370    pub(crate) invalid: usize,
371}
372
373impl From<Infallible> for InvalidLengthError {
374    #[inline]
375    fn from(never: Infallible) -> Self { match never {} }
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 fmt::Display for InvalidLengthError {
395    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
396        write!(
397            f,
398            // Note on singular vs plural: expected length is never odd, so it cannot be 1
399            "the hex string is {} bytes long but exactly {} bytes were required",
400            self.invalid_length(),
401            self.expected_length()
402        )
403    }
404}
405
406if_std_error! {{
407    impl StdError for InvalidLengthError {}
408}}
409
410#[cfg(test)]
411#[cfg(feature = "std")]
412mod tests {
413    use super::*;
414    use crate::{decode_to_array, decode_to_vec};
415
416    fn check_source<T: std::error::Error>(error: &T) {
417        assert!(error.source().is_some());
418    }
419
420    #[cfg(feature = "alloc")]
421    #[test]
422    fn invalid_char_error() {
423        let result = decode_to_vec("12G4");
424        let error = result.unwrap_err();
425        if let DecodeVariableLengthBytesError::InvalidChar(e) = error {
426            assert!(!format!("{}", e).is_empty());
427            assert_eq!(e.invalid_char(), b'G');
428            assert_eq!(e.pos(), 2);
429        } else {
430            panic!("Expected InvalidCharError");
431        }
432    }
433
434    #[cfg(feature = "alloc")]
435    #[test]
436    fn odd_length_string_error() {
437        let result = decode_to_vec("123");
438        let error = result.unwrap_err();
439        assert!(!format!("{}", error).is_empty());
440        check_source(&error);
441        if let DecodeVariableLengthBytesError::OddLengthString(e) = error {
442            assert!(!format!("{}", e).is_empty());
443            assert_eq!(e.length(), 3);
444        } else {
445            panic!("Expected OddLengthStringError");
446        }
447    }
448
449    #[test]
450    fn invalid_length_error() {
451        let result = decode_to_array::<4>("123");
452        let error = result.unwrap_err();
453        assert!(!format!("{}", error).is_empty());
454        check_source(&error);
455        if let DecodeFixedLengthBytesError::InvalidLength(e) = error {
456            assert!(!format!("{}", e).is_empty());
457            assert_eq!(e.expected_length(), 8);
458            assert_eq!(e.invalid_length(), 3);
459        } else {
460            panic!("Expected InvalidLengthError");
461        }
462    }
463
464    #[test]
465    fn to_bytes_error() {
466        let error =
467            DecodeVariableLengthBytesError::OddLengthString(OddLengthStringError { len: 7 });
468        assert!(!format!("{}", error).is_empty());
469        check_source(&error);
470    }
471
472    #[test]
473    fn to_array_error() {
474        let error = DecodeFixedLengthBytesError::InvalidLength(InvalidLengthError {
475            expected: 8,
476            invalid: 7,
477        });
478        assert!(!format!("{}", error).is_empty());
479        check_source(&error);
480    }
481}