// SPDX-License-Identifier: CC0-1.0 //! Parsing utilities. use core::fmt; use core::str::FromStr; use internals::error::InputString; use internals::write_err; /// Error with rich context returned when a string can't be parsed as an integer. /// /// This is an extension of [`core::num::ParseIntError`], which carries the input that failed to /// parse as well as type information. As a result it provides very informative error messages that /// make it easier to understand the problem and correct mistakes. /// /// Note that this is larger than the type from `core` so if it's passed through a deep call stack /// in a performance-critical application you may want to box it or throw away the context by /// converting to `core` type. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct ParseIntError { pub(crate) input: InputString, // for displaying - see Display impl with nice error message below pub(crate) bits: u8, // We could represent this as a single bit but it wouldn't actually derease the cost of moving // the struct because String contains pointers so there will be padding of bits at least // pointer_size - 1 bytes: min 1B in practice. pub(crate) is_signed: bool, pub(crate) source: core::num::ParseIntError, } impl fmt::Display for ParseIntError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let signed = if self.is_signed { "signed" } else { "unsigned" }; write_err!(f, "{} ({}, {}-bit)", self.input.display_cannot_parse("integer"), signed, self.bits; self.source) } } #[cfg(feature = "std")] impl std::error::Error for ParseIntError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) } } impl From for core::num::ParseIntError { fn from(value: ParseIntError) -> Self { value.source } } impl AsRef for ParseIntError { fn as_ref(&self) -> &core::num::ParseIntError { &self.source } } /// Not strictly necessary but serves as a lint - avoids weird behavior if someone accidentally /// passes non-integer to the `parse()` function. pub trait Integer: FromStr + TryFrom + Sized + sealed::Sealed { } macro_rules! impl_integer { ($($type:ty),* $(,)?) => { $( impl Integer for $type {} impl sealed::Sealed for $type {} )* } } impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128); mod sealed { /// Seals the extension traits. pub trait Sealed {} } /// Parses the input string as an integer returning an error carrying rich context. /// /// If the caller owns `String` or `Box` which is not used later it's better to pass it as /// owned since it avoids allocation in error case. pub fn int + Into>(s: S) -> Result { s.as_ref().parse().map_err(|error| { ParseIntError { input: s.into(), bits: u8::try_from(core::mem::size_of::() * 8).expect("max is 128 bits for u128"), // We detect if the type is signed by checking if -1 can be represented by it // this way we don't have to implement special traits and optimizer will get rid of the // computation. is_signed: T::try_from(-1i8).is_ok(), source: error, } }) } /// Implements `FromStr` and `TryFrom<{&str, String, Box}> for $to` using `parse::int`, mapping /// the output using infallible conversion function `fn`. /// /// The `Error` type is `ParseIntError` #[macro_export] macro_rules! impl_parse_str_from_int_infallible { ($to:ident, $inner:ident, $fn:ident) => { $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn); #[cfg(feature = "alloc")] $crate::impl_tryfrom_str_from_int_infallible!(alloc::string::String, $to, $inner, $fn; alloc::boxed::Box, $to, $inner, $fn); impl $crate::_export::_core::str::FromStr for $to { type Err = $crate::parse::ParseIntError; fn from_str(s: &str) -> $crate::_export::_core::result::Result { $crate::parse::int::<$inner, &str>(s).map($to::$fn) } } } } /// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using infallible /// conversion function `fn`. #[macro_export] #[doc(hidden)] // Helper macro called by `impl_parse_str_from_int_infallible`. macro_rules! impl_tryfrom_str_from_int_infallible { ($($from:ty, $to:ident, $inner:ident, $fn:ident);*) => { $( impl $crate::_export::_core::convert::TryFrom<$from> for $to { type Error = $crate::parse::ParseIntError; fn try_from(s: $from) -> $crate::_export::_core::result::Result { $crate::parse::int::<$inner, $from>(s).map($to::$fn) } } )* } } /// Implements `TryFrom<$from> for $to`. #[macro_export] macro_rules! impl_tryfrom_str { ($($from:ty, $to:ty, $err:ty, $inner_fn:expr);*) => { $( impl $crate::_export::_core::convert::TryFrom<$from> for $to { type Error = $err; fn try_from(s: $from) -> $crate::_export::_core::result::Result { $inner_fn(s) } } )* } } /// Implements standard parsing traits for `$type` by calling into `$inner_fn`. #[macro_export] macro_rules! impl_parse_str { ($to:ty, $err:ty, $inner_fn:expr) => { $crate::impl_tryfrom_str!(&str, $to, $err, $inner_fn); #[cfg(feature = "alloc")] $crate::impl_tryfrom_str!(alloc::string::String, $to, $err, $inner_fn; alloc::boxed::Box, $to, $err, $inner_fn); impl $crate::_export::_core::str::FromStr for $to { type Err = $err; fn from_str(s: &str) -> $crate::_export::_core::result::Result { $inner_fn(s) } } } } /// Removes the prefix `0x` (or `0X`) from a hex string. /// /// # Errors /// /// If the input string does not contain a prefix. pub fn hex_remove_prefix(s: &str) -> Result<&str, PrefixedHexError> { if let Some(checked) = s.strip_prefix("0x") { Ok(checked) } else if let Some(checked) = s.strip_prefix("0X") { Ok(checked) } else { Err(MissingPrefixError::new(s).into()) } } /// Checks a hex string does not have a prefix `0x` (or `0X`). /// /// # Errors /// /// If the input string contains a prefix. pub fn hex_check_unprefixed(s: &str) -> Result<&str, UnprefixedHexError> { if s.starts_with("0x") || s.starts_with("0X") { return Err(ContainsPrefixError::new(s).into()); } Ok(s) } /// Parses a `u32` from a hex string. /// /// Input string may or may not contain a `0x` (or `0X`) prefix. /// /// # Errors /// /// If the input string is not a valid hex encoding of a `u32`. pub fn hex_u32(s: &str) -> Result { let unchecked = hex_remove_optional_prefix(s); Ok(hex_u32_unchecked(unchecked)?) } /// Parses a `u32` from a prefixed hex string. /// /// # Errors /// /// - If the input string does not contain a `0x` (or `0X`) prefix. /// - If the input string is not a valid hex encoding of a `u32`. pub fn hex_u32_prefixed(s: &str) -> Result { let checked = hex_remove_prefix(s)?; Ok(hex_u32_unchecked(checked)?) } /// Parses a `u32` from an unprefixed hex string. /// /// # Errors /// /// - If the input string contains a `0x` (or `0X`) prefix. /// - If the input string is not a valid hex encoding of a `u32`. pub fn hex_u32_unprefixed(s: &str) -> Result { let checked = hex_check_unprefixed(s)?; Ok(hex_u32_unchecked(checked)?) } /// Parses a `u32` from an unprefixed hex string without first checking for a prefix. /// /// # Errors /// /// - If the input string contains a `0x` (or `0X`) prefix, returns `InvalidDigit` due to the `x`. /// - If the input string is not a valid hex encoding of a `u32`. pub fn hex_u32_unchecked(s: &str) -> Result { u32::from_str_radix(s, 16).map_err(|error| ParseIntError { input: s.into(), bits: 32, is_signed: false, source: error, }) } /// Parses a `u128` from a hex string. /// /// Input string may or may not contain a `0x` (or `0X`) prefix. /// /// # Errors /// /// If the input string is not a valid hex encoding of a `u128`. pub fn hex_u128(s: &str) -> Result { let unchecked = hex_remove_optional_prefix(s); Ok(hex_u128_unchecked(unchecked)?) } /// Parses a `u128` from a hex string. /// /// # Errors /// /// - If the input string does not contain a `0x` (or `0X`) prefix. /// - If the input string is not a valid hex encoding of a `u128`. pub fn hex_u128_prefixed(s: &str) -> Result { let checked = hex_remove_prefix(s)?; Ok(hex_u128_unchecked(checked)?) } /// Parses a `u128` from a hex string. /// /// # Errors /// /// - If the input string contains a `0x` (or `0X`) prefix. /// - If the input string is not a valid hex encoding of a `u128`. pub fn hex_u128_unprefixed(s: &str) -> Result { let checked = hex_check_unprefixed(s)?; Ok(hex_u128_unchecked(checked)?) } /// Parses a `u128` from an unprefixed hex string without first checking for a prefix. /// /// # Errors /// /// - If the input string contains a `0x` (or `0X`) prefix, returns `InvalidDigit` due to the `x`. /// - If the input string is not a valid hex encoding of a `u128`. pub fn hex_u128_unchecked(s: &str) -> Result { u128::from_str_radix(s, 16).map_err(|error| ParseIntError { input: s.into(), bits: 128, is_signed: false, source: error, }) } /// Strips the hex prefix off `s` if one is present. pub(crate) fn hex_remove_optional_prefix(s: &str) -> &str { if let Some(stripped) = s.strip_prefix("0x") { stripped } else if let Some(stripped) = s.strip_prefix("0X") { stripped } else { s } } /// Error returned when parsing an integer from a hex string that is supposed to contain a prefix. #[derive(Debug, Clone, Eq, PartialEq)] pub struct PrefixedHexError(PrefixedHexErrorInner); /// Error returned when parsing an integer from a hex string that is supposed to contain a prefix. #[derive(Debug, Clone, Eq, PartialEq)] enum PrefixedHexErrorInner { /// Hex string is missing prefix. MissingPrefix(MissingPrefixError), /// Error parsing integer from hex string. ParseInt(ParseIntError), } internals::impl_from_infallible!(PrefixedHexError); internals::impl_from_infallible!(PrefixedHexErrorInner); impl fmt::Display for PrefixedHexError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use PrefixedHexErrorInner as E; match self.0 { E::MissingPrefix(ref e) => write_err!(f, "hex string is missing prefix"; e), E::ParseInt(ref e) => write_err!(f, "prefixed hex string invalid int"; e), } } } #[cfg(feature = "std")] impl std::error::Error for PrefixedHexError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use PrefixedHexErrorInner as E; match self.0 { E::MissingPrefix(ref e) => Some(e), E::ParseInt(ref e) => Some(e), } } } impl From for PrefixedHexError { fn from(e: MissingPrefixError) -> Self { Self(PrefixedHexErrorInner::MissingPrefix(e)) } } impl From for PrefixedHexError { fn from(e: ParseIntError) -> Self { Self(PrefixedHexErrorInner::ParseInt(e)) } } /// Error returned when parsing an integer from a hex string that is not supposed to contain a prefix. #[derive(Debug, Clone, Eq, PartialEq)] pub struct UnprefixedHexError(UnprefixedHexErrorInner); #[derive(Debug, Clone, Eq, PartialEq)] enum UnprefixedHexErrorInner { /// Hex string contains prefix. ContainsPrefix(ContainsPrefixError), /// Error parsing integer from string. ParseInt(ParseIntError), } internals::impl_from_infallible!(UnprefixedHexError); internals::impl_from_infallible!(UnprefixedHexErrorInner); impl fmt::Display for UnprefixedHexError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use UnprefixedHexErrorInner as E; match self.0 { E::ContainsPrefix(ref e) => write_err!(f, "hex string is contains prefix"; e), E::ParseInt(ref e) => write_err!(f, "hex string parse int"; e), } } } #[cfg(feature = "std")] impl std::error::Error for UnprefixedHexError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use UnprefixedHexErrorInner as E; match self.0 { E::ContainsPrefix(ref e) => Some(e), E::ParseInt(ref e) => Some(e), } } } impl From for UnprefixedHexError { fn from(e: ContainsPrefixError) -> Self { Self(UnprefixedHexErrorInner::ContainsPrefix(e)) } } impl From for UnprefixedHexError { fn from(e: ParseIntError) -> Self { Self(UnprefixedHexErrorInner::ParseInt(e)) } } /// Error returned when a hex string is missing a prefix (e.g. `0x`). #[derive(Debug, Clone, Eq, PartialEq)] struct MissingPrefixError { hex: InputString, } impl MissingPrefixError { /// Constructs a new error from the string with the missing prefix. pub(crate) fn new(hex: &str) -> Self { Self { hex: hex.into() } } } impl fmt::Display for MissingPrefixError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} because it is missing the '0x' prefix", self.hex.display_cannot_parse("hex")) } } #[cfg(feature = "std")] impl std::error::Error for MissingPrefixError {} /// Error when hex string contains a prefix (e.g. 0x). #[derive(Debug, Clone, Eq, PartialEq)] struct ContainsPrefixError { hex: InputString, } impl ContainsPrefixError { /// Constructs a new error from the string that contains the prefix. pub(crate) fn new(hex: &str) -> Self { Self { hex: hex.into() } } } impl fmt::Display for ContainsPrefixError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} because it contains the '0x' prefix", self.hex.display_cannot_parse("hex")) } } #[cfg(feature = "std")] impl std::error::Error for ContainsPrefixError {} #[cfg(test)] mod tests { use super::*; #[test] fn parse_u32_from_hex_prefixed() { let want = 171; let got = hex_u32("0xab").expect("failed to parse prefixed hex"); assert_eq!(got, want); } #[test] fn parse_u32_from_hex_no_prefix() { let want = 171; let got = hex_u32("ab").expect("failed to parse non-prefixed hex"); assert_eq!(got, want); } #[test] fn parse_u128_from_hex_prefixed() { let want = 3_735_928_559; let got = hex_u128("0xdeadbeef").expect("failed to parse prefixed hex"); assert_eq!(got, want); } #[test] fn parse_u128_from_hex_no_prefix() { let want = 3_735_928_559; let got = hex_u128("deadbeef").expect("failed to parse non-prefixed hex"); assert_eq!(got, want); } #[test] fn parse_u32_from_hex_unchecked_errors_on_prefix() { assert!(hex_u32_unchecked("0xab").is_err()); } }