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