1use core::str::FromStr;
6
7#[rustfmt::skip] #[doc(no_inline)]
9pub use self::error::{ParseIntError, PrefixedHexError, UnprefixedHexError};
10
11pub 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 pub trait Sealed {}
35}
36
37#[inline]
57pub fn int_from_str<T: Integer>(s: &str) -> Result<T, ParseIntError> { int(s) }
58
59#[inline]
66#[cfg(feature = "alloc")]
67pub fn int_from_string<T: Integer>(s: alloc::string::String) -> Result<T, ParseIntError> { int(s) }
68
69#[inline]
76#[cfg(feature = "alloc")]
77pub fn int_from_box<T: Integer>(s: alloc::boxed::Box<str>) -> Result<T, ParseIntError> { int(s) }
78
79#[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 is_signed: T::try_from(-1i8).is_ok(),
91 truncated,
92 source: error,
93 }
94 })
95}
96
97macro_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
172macro_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
216macro_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#[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#[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
266macro_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 if !s.is_ascii() || digits.contains('+') {
389 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#[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
428pub mod error {
430 use core::convert::Infallible;
431 use core::fmt;
432
433 use internals::error::InputString;
434 use internals::write_err;
435
436 pub(super) const ERROR_STRING_LEN_LIMIT: usize = 80;
439
440 pub(crate) trait BoundedInput: Into<InputString> {
447 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 #[derive(Debug, Clone, PartialEq, Eq)]
490 #[non_exhaustive]
491 pub struct ParseIntError {
492 pub(crate) input: InputString,
493 pub(crate) bits: u16,
495 pub(crate) is_signed: bool,
499 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 #[derive(Debug, Clone, Eq, PartialEq)]
536 pub struct PrefixedHexError(pub(super) PrefixedHexErrorInner);
537
538 #[derive(Debug, Clone, Eq, PartialEq)]
540 pub(super) enum PrefixedHexErrorInner {
541 MissingPrefix(MissingPrefixError),
543 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 #[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 ContainsPrefix(ContainsPrefixError),
585 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 #[derive(Debug, Clone, Eq, PartialEq)]
621 pub(super) struct MissingPrefixError {
622 hex: InputString,
623 truncated: bool,
624 }
625
626 impl MissingPrefixError {
627 #[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 #[derive(Debug, Clone, Eq, PartialEq)]
659 pub(super) struct ContainsPrefixError {
660 hex: InputString,
661 truncated: bool,
662 }
663
664 impl ContainsPrefixError {
665 #[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 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 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 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 #[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; 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; 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; 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 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 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 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 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 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}