Skip to main content

bitcoin_units/
parse_int.rs

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