Skip to main content

bitcoin_units/
parse_int.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Parsing utilities.
4
5use core::str::FromStr;
6
7#[rustfmt::skip]                // Keep public re-exports separate.
8#[doc(no_inline)]
9pub use self::error::{ParseIntError, PrefixedHexError, UnprefixedHexError};
10
11/// Not strictly necessary but serves as a lint - avoids weird behavior if someone accidentally
12/// passes non-integer to the `parse()` function.
13// This trait is not dyn-compatible because `FromStr` is not dyn-compatible.
14pub trait Integer:
15    FromStr<Err = core::num::ParseIntError> + TryFrom<i8> + Sized + sealed::Sealed
16{
17}
18
19macro_rules! impl_integer {
20    ($($type:ty),* $(,)?) => {
21        $(
22        impl Integer for $type {}
23        impl sealed::Sealed for $type {}
24        )*
25    }
26}
27
28impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128);
29
30mod sealed {
31    /// Seals the [`Integer`] trait.
32    ///
33    /// [`Integer`]: super::Integer
34    pub trait Sealed {}
35}
36
37/// Parses the input string as an integer returning an error carrying rich context.
38///
39/// Apart from the rich error context this function exists so that we can handle builds with and
40/// without an allocator. If an allocator is available (`alloc` feature enabled) then this function
41/// allocates to copy the input string into the error return. If `alloc` is not enabled the input
42/// string is lost.
43///
44/// If the caller has a [`String`] or [`Box<str>`] which is not used later it's better to call
45/// [`parse_int::int_from_string`] or [`parse_int::int_from_box`] respectively.
46///
47/// [`String`]: alloc::string::String
48/// [`Box<str>`]: alloc::boxed::Box
49/// [`parse_int::int_from_string`]: crate::parse_int::int_from_string
50/// [`parse_int::int_from_box`]: crate::parse_int::int_from_box
51///
52/// # Errors
53///
54/// On error this function allocates to copy the input string into the error return. Only a bounded
55/// prefix of an over-long input is retained.
56#[inline]
57pub fn int_from_str<T: Integer>(s: &str) -> Result<T, ParseIntError> { int(s) }
58
59/// Parses the input string as an integer returning an error carrying rich context.
60///
61/// # Errors
62///
63/// On error the input string is moved into the error return without allocating, retaining it in
64/// full.
65#[inline]
66#[cfg(feature = "alloc")]
67pub fn int_from_string<T: Integer>(s: alloc::string::String) -> Result<T, ParseIntError> { int(s) }
68
69/// Parses the input string as an integer returning an error carrying rich context.
70///
71/// # Errors
72///
73/// On error the input string is converted into the error return without allocating, retaining it
74/// in full.
75#[inline]
76#[cfg(feature = "alloc")]
77pub fn int_from_box<T: Integer>(s: alloc::boxed::Box<str>) -> Result<T, ParseIntError> { int(s) }
78
79// This must be private because we do not want `InputString` to appear in the public API.
80#[inline]
81fn int<T: Integer, S: AsRef<str> + error::BoundedInput>(s: S) -> Result<T, ParseIntError> {
82    s.as_ref().parse().map_err(|error| {
83        let (input, truncated) = s.into_bounded_input();
84        ParseIntError {
85            input,
86            bits: u16::try_from(core::mem::size_of::<T>() * 8).expect("max is 128 bits for u128"),
87            // We detect if the type is signed by checking if -1 can be represented by it
88            // this way we don't have to implement special traits and optimizer will get rid of the
89            // computation.
90            is_signed: T::try_from(-1i8).is_ok(),
91            truncated,
92            source: error,
93        }
94    })
95}
96
97/// Implements standard parsing traits for `$type` by calling `parse::int`.
98///
99/// Once the string is converted to an integer the infallible conversion function `fn` is used to
100/// construct the type `to`.
101///
102/// Implements:
103///
104/// * [`FromStr`]
105/// * [`TryFrom<&str>`]
106///
107/// And if `alloc` feature is enabled in calling crate:
108///
109/// * [`TryFrom<Box<str>>`]
110/// * [`TryFrom<String>`]
111///
112/// # Parameters
113///
114/// * `to` - the type converted to e.g., `impl From<&str> for $to`.
115/// * `inner` - the inner integer type to be provided to `fn`. Must implement [`Integer`].
116/// * `fn`: the infallible conversion function to call to convert from an integer.
117///
118/// # Errors
119///
120/// If parsing the string fails then a [`ParseIntError`] is returned.
121///
122/// [`TryFrom<&str>`]: core::convert::TryFrom
123/// [`TryFrom<Box<str>>`]: core::convert::TryFrom
124/// [`TryFrom<String>`]: core::convert::TryFrom
125macro_rules! impl_parse_str_from_int_infallible {
126    ($to:ident, $inner:ident, $fn:ident) => {
127        impl $crate::_export::_core::str::FromStr for $to {
128            type Err = $crate::parse_int::ParseIntError;
129
130            #[inline]
131            fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
132                $crate::_export::_core::convert::TryFrom::try_from(s)
133            }
134        }
135
136        impl $crate::_export::_core::convert::TryFrom<&str> for $to {
137            type Error = $crate::parse_int::ParseIntError;
138
139            #[inline]
140            fn try_from(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Error> {
141                $crate::parse_int::int_from_str::<$inner>(s).map($to::$fn)
142            }
143        }
144
145        #[cfg(feature = "alloc")]
146        impl $crate::_export::_core::convert::TryFrom<alloc::string::String> for $to {
147            type Error = $crate::parse_int::ParseIntError;
148
149            #[inline]
150            fn try_from(
151                s: alloc::string::String,
152            ) -> $crate::_export::_core::result::Result<Self, Self::Error> {
153                $crate::parse_int::int_from_string::<$inner>(s).map($to::$fn)
154            }
155        }
156
157        #[cfg(feature = "alloc")]
158        impl $crate::_export::_core::convert::TryFrom<alloc::boxed::Box<str>> for $to {
159            type Error = $crate::parse_int::ParseIntError;
160
161            #[inline]
162            fn try_from(
163                s: alloc::boxed::Box<str>,
164            ) -> $crate::_export::_core::result::Result<Self, Self::Error> {
165                $crate::parse_int::int_from_box::<$inner>(s).map($to::$fn)
166            }
167        }
168    };
169}
170pub(crate) use impl_parse_str_from_int_infallible;
171
172/// Implements standard parsing traits for `$type` by calling through to `$inner_fn`.
173///
174/// Implements:
175///
176/// * [`FromStr`]
177/// * [`TryFrom<&str>`]
178///
179/// And if `alloc` feature is enabled in calling crate:
180///
181/// * [`TryFrom<Box<str>>`]
182/// * [`TryFrom<String>`]
183///
184/// # Parameters
185///
186/// * `to` - the type converted to e.g., `impl From<&str> for $to`.
187/// * `err` - the error type returned by `$inner_fn` (implies returned by [`FromStr`] and
188///   [`TryFrom`]).
189/// * `inner_fn`: the fallible conversion function to call to convert from a string reference.
190///
191/// # Errors
192///
193/// All functions use the error returned by `$inner_fn`.
194///
195/// [`TryFrom<&str>`]: core::convert::TryFrom
196/// [`TryFrom<Box<str>>`]: core::convert::TryFrom
197/// [`TryFrom<String>`]: core::convert::TryFrom
198macro_rules! impl_parse_str {
199    ($to:ty, $err:ty, $inner_fn:expr) => {
200        $crate::parse_int::impl_tryfrom_str!(&str, $to, $err, $inner_fn);
201        #[cfg(feature = "alloc")]
202        $crate::parse_int::impl_tryfrom_str!(alloc::string::String, $to, $err, $inner_fn; alloc::boxed::Box<str>, $to, $err, $inner_fn);
203
204        impl $crate::_export::_core::str::FromStr for $to {
205            type Err = $err;
206
207            #[inline]
208            fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
209                $inner_fn(s)
210            }
211        }
212    }
213}
214pub(crate) use impl_parse_str;
215
216/// Implements `TryFrom<$from> for $to`.
217macro_rules! impl_tryfrom_str {
218    ($($from:ty, $to:ty, $err:ty, $inner_fn:expr);*) => {
219        $(
220            impl $crate::_export::_core::convert::TryFrom<$from> for $to {
221                type Error = $err;
222
223                #[inline]
224                fn try_from(s: $from) -> $crate::_export::_core::result::Result<Self, Self::Error> {
225                    $inner_fn(s)
226                }
227            }
228        )*
229    }
230}
231pub(crate) use impl_tryfrom_str;
232
233/// Removes the prefix `0x` (or `0X`) from a hex string.
234///
235/// # Errors
236///
237/// If the input string does not contain a prefix.
238#[inline]
239pub(crate) fn hex_remove_prefix(s: &str) -> Result<&str, PrefixedHexError> {
240    if let Some(checked) = s.strip_prefix("0x") {
241        Ok(checked)
242    } else if let Some(checked) = s.strip_prefix("0X") {
243        Ok(checked)
244    } else {
245        Err(error::MissingPrefixError::new(s))
246            .map_err(error::PrefixedHexErrorInner::MissingPrefix)
247            .map_err(PrefixedHexError)
248    }
249}
250
251/// Checks a hex string does not have a prefix `0x` (or `0X`).
252///
253/// # Errors
254///
255/// If the input string contains a prefix.
256#[inline]
257pub(crate) fn hex_check_unprefixed(s: &str) -> Result<&str, UnprefixedHexError> {
258    if s.starts_with("0x") || s.starts_with("0X") {
259        return Err(error::ContainsPrefixError::new(s))
260            .map_err(error::UnprefixedHexErrorInner::ContainsPrefix)
261            .map_err(UnprefixedHexError);
262    }
263    Ok(s)
264}
265
266/// Macro to generate parsing functions for a given unsigned type
267macro_rules! parse_hex_for {
268    (
269        $int_type:ty, $bits:literal;
270        fn $any_hex_fn:ident();
271        fn $prefix_hex_fn:ident();
272        fn $unprefix_hex_fn:ident();
273        fn $uncheck_hex_fn:ident();
274    ) => {
275        #[doc = "Parses a `"]
276        #[doc = stringify!($int_type)]
277        #[doc = "` from a hex string.\n\n"]
278        #[doc = "Input string may or may not contain a `0x` (or `0X`) prefix.\n\n"]
279        #[doc = "# Errors\n\nIf the input string is not a valid hex encoding of a `"]
280        #[doc = stringify!($int_type)]
281        #[doc = "`."]
282        #[inline]
283        pub fn $any_hex_fn(s: &str) -> Result<$int_type, ParseIntError> {
284            let unchecked = hex_remove_optional_prefix(s);
285            $uncheck_hex_fn(unchecked)
286        }
287
288        #[doc = "Parses a `"]
289        #[doc = stringify!($int_type)]
290        #[doc = "` from a prefixed hex string.\n\n"]
291        #[doc = "# Errors\n\n"]
292        #[doc = "- If the input string does not contain a `0x` (or `0X`) prefix.\n"]
293        #[doc = "- If the input string is not a valid hex encoding of a `"]
294        #[doc = stringify!($int_type)]
295        #[doc = "`."]
296        #[inline]
297        pub fn $prefix_hex_fn(s: &str) -> Result<$int_type, PrefixedHexError> {
298            let checked = hex_remove_prefix(s)?;
299            $uncheck_hex_fn(checked)
300                .map_err(error::PrefixedHexErrorInner::ParseInt)
301                .map_err(PrefixedHexError)
302        }
303
304        #[doc = "Parses a `"]
305        #[doc = stringify!($int_type)]
306        #[doc = "` from an unprefixed hex string.\n\n"]
307        #[doc = "# Errors\n\n"]
308        #[doc = "- If the input string contains a `0x` (or `0X`) prefix.\n"]
309        #[doc = "- If the input string is not a valid hex encoding of a `"]
310        #[doc = stringify!($int_type)]
311        #[doc = "`."]
312        #[inline]
313        pub fn $unprefix_hex_fn(s: &str) -> Result<$int_type, UnprefixedHexError> {
314            let checked = hex_check_unprefixed(s)?;
315            $uncheck_hex_fn(checked)
316                .map_err(error::UnprefixedHexErrorInner::ParseInt)
317                .map_err(UnprefixedHexError)
318        }
319
320        #[doc = "Parses a `"]
321        #[doc = stringify!($int_type)]
322        #[doc = "` from an unprefixed hex string without first checking for a prefix.\n\n"]
323        #[doc = "# Errors\n\n"]
324        #[doc = "- If the input string contains a `0x` (or `0X`) prefix,"]
325        #[doc = " returns `InvalidDigit` due to the `x`.\n"]
326        #[doc = "- If the input string is not a valid hex encoding of a `"]
327        #[doc = stringify!($int_type)]
328        #[doc = "`."]
329        #[inline]
330        fn $uncheck_hex_fn(s: &str) -> Result<$int_type, ParseIntError> {
331            <$int_type>::from_str_radix(s, 16).map_err(|error| {
332                let (input, truncated) = error::BoundedInput::into_bounded_input(s);
333                ParseIntError { input, bits: $bits, is_signed: false, truncated, source: error }
334            })
335        }
336    };
337}
338
339parse_hex_for!(
340    u16, 16;
341    fn hex_u16();
342    fn hex_u16_prefixed();
343    fn hex_u16_unprefixed();
344    fn hex_u16_unchecked();
345);
346parse_hex_for!(
347    u32, 32;
348    fn hex_u32();
349    fn hex_u32_prefixed();
350    fn hex_u32_unprefixed();
351    fn hex_u32_unchecked();
352);
353parse_hex_for!(
354    u64, 64;
355    fn hex_u64();
356    fn hex_u64_prefixed();
357    fn hex_u64_unprefixed();
358    fn hex_u64_unchecked();
359);
360parse_hex_for!(
361    u128, 128;
362    fn hex_u128();
363    fn hex_u128_prefixed();
364    fn hex_u128_unprefixed();
365    fn hex_u128_unchecked();
366);
367
368#[inline]
369pub(crate) fn hex_u256_prefixed(s: &str) -> Result<internals::u256::U256, PrefixedHexError> {
370    let checked = hex_remove_prefix(s)?;
371    hex_u256_unchecked(checked)
372        .map_err(error::PrefixedHexErrorInner::ParseInt)
373        .map_err(PrefixedHexError)
374}
375
376#[inline]
377pub(crate) fn hex_u256_unprefixed(s: &str) -> Result<internals::u256::U256, UnprefixedHexError> {
378    let checked = hex_check_unprefixed(s)?;
379    hex_u256_unchecked(checked)
380        .map_err(error::UnprefixedHexErrorInner::ParseInt)
381        .map_err(UnprefixedHexError)
382}
383
384pub(crate) fn hex_u256_unchecked(s: &str) -> Result<internals::u256::U256, ParseIntError> {
385    let digits = s.strip_prefix('+').unwrap_or(s);
386    // We cannot use byte offsets into `s` if it is not ASCII, and the halves must not start with
387    // a sign.
388    if !s.is_ascii() || digits.contains('+') {
389        // We want the `ParseIntError`; use u128 to get it since we know the string is invalid.
390        return u128::from_str_radix(s, 16)
391            .map_err(|error| {
392                let (input, truncated) = error::BoundedInput::into_bounded_input(s);
393                ParseIntError { input, bits: 256, is_signed: false, truncated, source: error }
394            })
395            .map(internals::u256::U256::from);
396    }
397    let (high, low) = if digits.len() <= 32 {
398        let low = hex_u128_unchecked(s)?;
399        (0, low)
400    } else {
401        let high_len = digits.len() - 32;
402        let high_s = &digits[..high_len];
403        let low_s = &digits[high_len..];
404
405        let high = hex_u128_unchecked(high_s)?;
406        let low = hex_u128_unchecked(low_s)?;
407        (high, low)
408    };
409
410    let mut bytes = [0u8; 32];
411    bytes[..16].copy_from_slice(&low.to_le_bytes());
412    bytes[16..].copy_from_slice(&high.to_le_bytes());
413    Ok(internals::u256::U256::from_le_bytes(bytes))
414}
415
416/// Strips the hex prefix off `s` if one is present.
417#[inline]
418pub(crate) fn hex_remove_optional_prefix(s: &str) -> &str {
419    if let Some(stripped) = s.strip_prefix("0x") {
420        stripped
421    } else if let Some(stripped) = s.strip_prefix("0X") {
422        stripped
423    } else {
424        s
425    }
426}
427
428/// Error types for integer parsing utilities.
429pub mod error {
430    use core::convert::Infallible;
431    use core::fmt;
432
433    use internals::error::InputString;
434    use internals::write_err;
435
436    /// Maximum number of bytes of borrowed input retained by a parse error. Longer than any valid
437    /// input.
438    pub(super) const ERROR_STRING_LEN_LIMIT: usize = 80;
439
440    /// Converts the input of a parse function into an [`InputString`] for error context.
441    ///
442    /// Borrowed input is copied into the error, so an over-long input would otherwise force an
443    /// allocation proportional to its length; such input is truncated on a UTF-8 boundary to
444    /// [`ERROR_STRING_LEN_LIMIT`] bytes. Owned input is moved into the error without allocating, so
445    /// there is nothing to bound and it is retained in full.
446    pub(crate) trait BoundedInput: Into<InputString> {
447        /// Converts `self` into an [`InputString`], returning whether the retained input was
448        /// truncated.
449        fn into_bounded_input(self) -> (InputString, bool);
450    }
451
452    impl BoundedInput for &str {
453        fn into_bounded_input(self) -> (InputString, bool) {
454            if self.len() <= ERROR_STRING_LEN_LIMIT {
455                (self.into(), false)
456            } else {
457                let mut end = ERROR_STRING_LEN_LIMIT;
458                loop {
459                    if let Some(truncated) = self.get(..end) {
460                        break (truncated.into(), true);
461                    }
462                    end -= 1;
463                }
464            }
465        }
466    }
467
468    #[cfg(feature = "alloc")]
469    impl BoundedInput for alloc::string::String {
470        #[inline]
471        fn into_bounded_input(self) -> (InputString, bool) { (self.into(), false) }
472    }
473
474    #[cfg(feature = "alloc")]
475    impl BoundedInput for alloc::boxed::Box<str> {
476        #[inline]
477        fn into_bounded_input(self) -> (InputString, bool) { (self.into(), false) }
478    }
479
480    /// Error with rich context returned when a string can't be parsed as an integer.
481    ///
482    /// This is an extension of [`core::num::ParseIntError`], which carries the input that failed to
483    /// parse as well as type information. As a result it provides very informative error messages that
484    /// make it easier to understand the problem and correct mistakes.
485    ///
486    /// Note that this is larger than the type from `core` so if it's passed through a deep call stack
487    /// in a performance-critical application you may want to box it or throw away the context by
488    /// converting to `core` type.
489    #[derive(Debug, Clone, PartialEq, Eq)]
490    #[non_exhaustive]
491    pub struct ParseIntError {
492        pub(crate) input: InputString,
493        // for displaying - see Display impl with nice error message below
494        pub(crate) bits: u16,
495        // We could represent this as a single bit, but it wouldn't actually decrease the cost of moving
496        // the struct because String contains pointers so there will be padding of bits at least
497        // pointer_size - 1 bytes: min 1B in practice.
498        pub(crate) is_signed: bool,
499        // Set when `input` was truncated because the borrowed input exceeded `ERROR_STRING_LEN_LIMIT`.
500        pub(crate) truncated: bool,
501        pub(crate) source: core::num::ParseIntError,
502    }
503
504    impl From<Infallible> for ParseIntError {
505        #[inline]
506        fn from(never: Infallible) -> Self { match never {} }
507    }
508
509    impl fmt::Display for ParseIntError {
510        #[inline]
511        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
512            let signed = if self.is_signed { "signed" } else { "unsigned" };
513            let truncated = if self.truncated { "..." } else { "" };
514            write_err!(f, "{}{} ({}, {}-bit)", self.input.display_cannot_parse("integer"), truncated, signed, self.bits; self.source)
515        }
516    }
517
518    #[cfg(feature = "std")]
519    impl std::error::Error for ParseIntError {
520        #[inline]
521        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) }
522    }
523
524    impl From<ParseIntError> for core::num::ParseIntError {
525        #[inline]
526        fn from(value: ParseIntError) -> Self { value.source }
527    }
528
529    impl AsRef<core::num::ParseIntError> for ParseIntError {
530        #[inline]
531        fn as_ref(&self) -> &core::num::ParseIntError { &self.source }
532    }
533
534    /// Error returned when parsing an integer from a hex string that is supposed to contain a prefix.
535    #[derive(Debug, Clone, Eq, PartialEq)]
536    pub struct PrefixedHexError(pub(super) PrefixedHexErrorInner);
537
538    /// Error returned when parsing an integer from a hex string that is supposed to contain a prefix.
539    #[derive(Debug, Clone, Eq, PartialEq)]
540    pub(super) enum PrefixedHexErrorInner {
541        /// Hex string is missing prefix.
542        MissingPrefix(MissingPrefixError),
543        /// Error parsing integer from hex string.
544        ParseInt(ParseIntError),
545    }
546
547    impl From<Infallible> for PrefixedHexError {
548        #[inline]
549        fn from(never: Infallible) -> Self { match never {} }
550    }
551
552    impl fmt::Display for PrefixedHexError {
553        #[inline]
554        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
555            use PrefixedHexErrorInner as E;
556
557            match self.0 {
558                E::MissingPrefix(ref e) => write_err!(f, "hex string is missing prefix"; e),
559                E::ParseInt(ref e) => write_err!(f, "prefixed hex string invalid int"; e),
560            }
561        }
562    }
563
564    #[cfg(feature = "std")]
565    impl std::error::Error for PrefixedHexError {
566        #[inline]
567        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
568            use PrefixedHexErrorInner as E;
569
570            match self.0 {
571                E::MissingPrefix(ref e) => Some(e),
572                E::ParseInt(ref e) => Some(e),
573            }
574        }
575    }
576
577    /// Error returned when parsing an integer from a hex string that is not supposed to contain a prefix.
578    #[derive(Debug, Clone, Eq, PartialEq)]
579    pub struct UnprefixedHexError(pub(super) UnprefixedHexErrorInner);
580
581    #[derive(Debug, Clone, Eq, PartialEq)]
582    pub(super) enum UnprefixedHexErrorInner {
583        /// Hex string contains prefix.
584        ContainsPrefix(ContainsPrefixError),
585        /// Error parsing integer from string.
586        ParseInt(ParseIntError),
587    }
588
589    impl From<Infallible> for UnprefixedHexError {
590        #[inline]
591        fn from(never: Infallible) -> Self { match never {} }
592    }
593
594    impl fmt::Display for UnprefixedHexError {
595        #[inline]
596        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
597            use UnprefixedHexErrorInner as E;
598
599            match self.0 {
600                E::ContainsPrefix(ref e) => write_err!(f, "hex string contains prefix"; e),
601                E::ParseInt(ref e) => write_err!(f, "hex string parse int"; e),
602            }
603        }
604    }
605
606    #[cfg(feature = "std")]
607    impl std::error::Error for UnprefixedHexError {
608        #[inline]
609        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
610            use UnprefixedHexErrorInner as E;
611
612            match self.0 {
613                E::ContainsPrefix(ref e) => Some(e),
614                E::ParseInt(ref e) => Some(e),
615            }
616        }
617    }
618
619    /// Error returned when a hex string is missing a prefix (e.g. `0x`).
620    #[derive(Debug, Clone, Eq, PartialEq)]
621    pub(super) struct MissingPrefixError {
622        hex: InputString,
623        truncated: bool,
624    }
625
626    impl MissingPrefixError {
627        /// Constructs a new error from the string with the missing prefix.
628        #[inline]
629        pub(crate) fn new(hex: &str) -> Self {
630            let (hex, truncated) = hex.into_bounded_input();
631            Self { hex, truncated }
632        }
633    }
634
635    impl fmt::Display for MissingPrefixError {
636        #[inline]
637        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
638            let truncated = if self.truncated { "..." } else { "" };
639            write!(
640                f,
641                "{}{} because it is missing the '0x' prefix",
642                self.hex.display_cannot_parse("hex"),
643                truncated
644            )
645        }
646    }
647
648    #[cfg(feature = "std")]
649    impl std::error::Error for MissingPrefixError {
650        #[inline]
651        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
652            let Self { hex: _, truncated: _ } = self;
653            None
654        }
655    }
656
657    /// Error when hex string contains a prefix (e.g. 0x).
658    #[derive(Debug, Clone, Eq, PartialEq)]
659    pub(super) struct ContainsPrefixError {
660        hex: InputString,
661        truncated: bool,
662    }
663
664    impl ContainsPrefixError {
665        /// Constructs a new error from the string that contains the prefix.
666        #[inline]
667        pub(crate) fn new(hex: &str) -> Self {
668            let (hex, truncated) = hex.into_bounded_input();
669            Self { hex, truncated }
670        }
671    }
672
673    impl fmt::Display for ContainsPrefixError {
674        #[inline]
675        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
676            let truncated = if self.truncated { "..." } else { "" };
677            write!(
678                f,
679                "{}{} because it contains the '0x' prefix",
680                self.hex.display_cannot_parse("hex"),
681                truncated
682            )
683        }
684    }
685
686    #[cfg(feature = "std")]
687    impl std::error::Error for ContainsPrefixError {
688        #[inline]
689        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
690            let Self { hex: _, truncated: _ } = self;
691            None
692        }
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    #[cfg(feature = "alloc")]
699    use alloc::format;
700    #[cfg(feature = "alloc")]
701    use alloc::string::ToString;
702    #[cfg(feature = "std")]
703    use std::{error::Error, panic};
704
705    use super::*;
706
707    #[test]
708    fn parse_int() {
709        assert!(int_from_str::<u8>("1").is_ok());
710        let _ = int_from_str::<i8>("not a number").map_err(|e| assert!(e.is_signed));
711        let _ = int_from_str::<u8>("not a number").map_err(|e| assert!(!e.is_signed));
712    }
713
714    #[test]
715    #[cfg(feature = "alloc")]
716    fn parse_int_error_bounds_borrowed_input() {
717        let long = "f".repeat(error::ERROR_STRING_LEN_LIMIT + 100);
718
719        // Borrowed input over the limit is truncated and the marker is shown in `Display`.
720        let err = int_from_str::<u32>(&long).unwrap_err();
721        assert!(err.truncated);
722        assert!(format!("{err}").contains("..."));
723        assert!(!format!("{err:?}").contains(&long));
724
725        // Short borrowed input is retained as-is with no marker.
726        let err = int_from_str::<u32>("nope").unwrap_err();
727        assert!(!err.truncated);
728        assert!(!format!("{err}").contains("..."));
729    }
730
731    #[test]
732    #[cfg(feature = "alloc")]
733    fn parse_int_error_retains_owned_input_in_full() {
734        // Owned input is moved into the error without truncation, even when over the limit.
735        let long = "f".repeat(error::ERROR_STRING_LEN_LIMIT + 100);
736        let err = int_from_string::<u32>(long.clone()).unwrap_err();
737        assert!(!err.truncated);
738        assert!(format!("{err:?}").contains(&long));
739    }
740
741    #[test]
742    #[cfg(feature = "std")]
743    fn parse_int_panic_when_populating_bits() {
744        // Fields in the test type are never used
745        #[allow(dead_code)]
746        struct TestTypeWithMoreThanU16Bits([u8; 8192]);
747        impl_integer!(TestTypeWithMoreThanU16Bits);
748        impl FromStr for TestTypeWithMoreThanU16Bits {
749            type Err = core::num::ParseIntError;
750
751            fn from_str(_: &str) -> Result<Self, Self::Err> {
752                "Always invalid for testing".parse::<u32>().map(|_| Self([0; 8192]))
753            }
754        }
755        impl From<i8> for TestTypeWithMoreThanU16Bits {
756            fn from(_: i8) -> Self { Self([0; 8192]) }
757        }
758
759        let result =
760            panic::catch_unwind(|| int_from_str::<TestTypeWithMoreThanU16Bits>("not a number"));
761        assert!(result.is_err());
762    }
763
764    #[test]
765    fn remove_prefix() {
766        let lower = "0xhello";
767        assert_eq!(hex_remove_prefix(lower).unwrap(), "hello");
768
769        let upper = "0Xhello";
770        assert_eq!(hex_remove_prefix(upper).unwrap(), "hello");
771
772        let err = "error";
773        assert!(hex_remove_prefix(err).is_err());
774    }
775
776    #[test]
777    fn check_unprefixed() {
778        let lower = "0xhello";
779        assert!(hex_check_unprefixed(lower).is_err());
780
781        let upper = "0Xhello";
782        assert!(hex_check_unprefixed(upper).is_err());
783
784        let valid = "hello";
785        assert_eq!(hex_check_unprefixed(valid).unwrap(), "hello");
786    }
787
788    #[test]
789    fn parse_u32_from_hex_prefixed() {
790        let want = 171;
791        let got = hex_u32("0xab").expect("failed to parse prefixed hex");
792        assert_eq!(got, want);
793    }
794
795    #[test]
796    fn parse_u32_from_hex_prefixed_upper() {
797        let want = 171;
798        let got = hex_u32("0XAB").expect("failed to parse prefixed hex");
799        assert_eq!(got, want);
800    }
801
802    #[test]
803    fn parse_u32_from_hex_no_prefix() {
804        let want = 171;
805        let got = hex_u32("ab").expect("failed to parse non-prefixed hex");
806        assert_eq!(got, want);
807    }
808
809    #[test]
810    fn parse_hex_u32_prefixed() {
811        let want = 171; // 0xab
812        assert_eq!(hex_u32_prefixed("0xab").unwrap(), want);
813        assert!(hex_u32_unprefixed("0xab").is_err());
814    }
815
816    #[test]
817    fn parse_hex_u32_upper_prefixed() {
818        let want = 171; // 0xab
819        assert_eq!(hex_u32_prefixed("0Xab").unwrap(), want);
820        assert!(hex_u32_unprefixed("0Xab").is_err());
821    }
822
823    #[test]
824    fn parse_hex_u32_unprefixed() {
825        let want = 171; // 0xab
826        assert_eq!(hex_u32_unprefixed("ab").unwrap(), want);
827        assert!(hex_u32_prefixed("ab").is_err());
828    }
829
830    #[test]
831    fn parse_u128_from_hex_prefixed() {
832        let want = 3_735_928_559;
833        let got = hex_u128("0xdeadbeef").expect("failed to parse prefixed hex");
834        assert_eq!(got, want);
835    }
836
837    #[test]
838    fn parse_u128_from_hex_upper_prefixed() {
839        let want = 3_735_928_559;
840        let got = hex_u128("0Xdeadbeef").expect("failed to parse prefixed hex");
841        assert_eq!(got, want);
842    }
843
844    #[test]
845    fn parse_u128_from_hex_no_prefix() {
846        let want = 3_735_928_559;
847        let got = hex_u128("deadbeef").expect("failed to parse non-prefixed hex");
848        assert_eq!(got, want);
849    }
850
851    #[test]
852    fn parse_hex_u128_prefixed() {
853        let want = 3_735_928_559;
854        assert_eq!(hex_u128_prefixed("0xdeadbeef").unwrap(), want);
855        assert!(hex_u128_unprefixed("0xdeadbeef").is_err());
856    }
857
858    #[test]
859    fn parse_hex_u128_upper_prefixed() {
860        let want = 3_735_928_559;
861        assert_eq!(hex_u128_prefixed("0Xdeadbeef").unwrap(), want);
862        assert!(hex_u128_unprefixed("0Xdeadbeef").is_err());
863    }
864
865    #[test]
866    fn parse_hex_u128_unprefixed() {
867        let want = 3_735_928_559;
868        assert_eq!(hex_u128_unprefixed("deadbeef").unwrap(), want);
869        assert!(hex_u128_prefixed("deadbeef").is_err());
870    }
871
872    #[test]
873    fn parse_u32_from_hex_unchecked_errors_on_prefix() {
874        assert!(hex_u32_unchecked("0xab").is_err());
875        assert!(hex_u32_unchecked("0Xab").is_err());
876    }
877
878    #[test]
879    fn parse_u32_from_hex_unchecked_errors_on_overflow() {
880        assert!(hex_u32_unchecked("1234abcd").is_ok());
881        assert!(hex_u32_unchecked("1234abcd1").is_err());
882    }
883
884    #[test]
885    fn parse_u128_from_hex_unchecked_errors_on_prefix() {
886        assert!(hex_u128_unchecked("0xdeadbeef").is_err());
887        assert!(hex_u128_unchecked("0Xdeadbeef").is_err());
888    }
889
890    #[test]
891    fn parse_u128_from_hex_unchecked_errors_on_overflow() {
892        assert!(hex_u128_unchecked("deadbeefabcdffffdeadbeefabcdffff").is_ok());
893        assert!(hex_u128_unchecked("deadbeefabcdffffdeadbeefabcdffff1").is_err());
894    }
895
896    #[test]
897    fn parse_u256_from_hex_non_ascii_reports_bit_length() {
898        let error = hex_u256_unchecked("é0000000000000000000000000000000").unwrap_err();
899        assert_eq!(error.bits, 256);
900    }
901
902    #[test]
903    #[cfg(feature = "alloc")]
904    fn missing_prefix_error_display_escapes_control_characters() {
905        let e = hex_u32_prefixed("bad\nlevel=INFO accepted=true").unwrap_err().to_string();
906        assert!(!e.chars().any(char::is_control));
907    }
908
909    #[test]
910    #[cfg(feature = "alloc")]
911    fn contains_prefix_error_display_escapes_control_characters() {
912        let e = hex_u32_unprefixed("0x1\nlevel=INFO accepted=true").unwrap_err().to_string();
913        assert!(!e.chars().any(char::is_control));
914    }
915
916    #[test]
917    #[cfg(feature = "alloc")]
918    fn error_display_is_non_empty() {
919        // ParseIntError - parse invalid integer
920        let e = int_from_str::<u32>("not_a_number").unwrap_err();
921        assert!(!e.to_string().is_empty());
922        #[cfg(feature = "std")]
923        assert!(e.source().is_some());
924
925        // PrefixedHexError
926        // missing prefix type
927        let e = hex_u32_prefixed("abc").unwrap_err();
928        assert!(!e.to_string().is_empty());
929        #[cfg(feature = "std")]
930        assert!(e.source().is_some());
931        let PrefixedHexError(error::PrefixedHexErrorInner::MissingPrefix(e)) = e else {
932            panic!("should be a MissingPrefixError")
933        };
934        assert!(!e.to_string().is_empty());
935        #[cfg(feature = "std")]
936        assert!(e.source().is_none());
937        // bad number type
938        let e = hex_u32_prefixed("0xgabc").unwrap_err();
939        assert!(!e.to_string().is_empty());
940        #[cfg(feature = "std")]
941        assert!(e.source().is_some());
942
943        // UnprefixedHexError
944        // has prefix type
945        let e = hex_u32_unprefixed("0xabc").unwrap_err();
946        assert!(!e.to_string().is_empty());
947        #[cfg(feature = "std")]
948        assert!(e.source().is_some());
949        let UnprefixedHexError(error::UnprefixedHexErrorInner::ContainsPrefix(e)) = e else {
950            panic!("should be a ContainsPrefixError")
951        };
952        assert!(!e.to_string().is_empty());
953        #[cfg(feature = "std")]
954        assert!(e.source().is_none());
955        // bad number type
956        let e = hex_u32_unprefixed("gabc").unwrap_err();
957        assert!(!e.to_string().is_empty());
958        #[cfg(feature = "std")]
959        assert!(e.source().is_some());
960    }
961}