diff --git a/internals/src/error.rs b/internals/src/error/mod.rs similarity index 100% rename from internals/src/error.rs rename to internals/src/error/mod.rs diff --git a/units/src/locktime/absolute.rs b/units/src/locktime/absolute.rs index 69c80a52b..92283468d 100644 --- a/units/src/locktime/absolute.rs +++ b/units/src/locktime/absolute.rs @@ -6,11 +6,11 @@ use alloc::{boxed::Box, string::String}; use core::fmt; -use internals::write_err; +use internals::error::InputString; +use crate::parse::ParseIntError; #[cfg(feature = "alloc")] use crate::parse; -use crate::parse::ParseIntError; /// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]). /// @@ -204,7 +204,7 @@ pub struct ParseTimeError(ParseError); impl fmt::Display for ParseTimeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.display(f, "block height", LOCK_TIME_THRESHOLD, u32::MAX) + self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX) } } @@ -221,7 +221,7 @@ impl From for ParseTimeError { fn parser(f: F) -> impl FnOnce(S) -> Result where E: From, - S: AsRef + Into, + S: AsRef + Into, F: FnOnce(u32) -> Result, { move |s| { @@ -234,7 +234,7 @@ where fn parse_hex(s: S, f: F) -> Result where E: From, - S: AsRef + Into, + S: AsRef + Into, F: FnOnce(u32) -> Result, { let n = i64::from_str_radix(parse::hex_remove_optional_prefix(s.as_ref()), 16) @@ -301,7 +301,7 @@ impl fmt::Display for LockTimeUnit { /// Internal - common representation for height and time. #[derive(Debug, Clone, Eq, PartialEq)] enum ParseError { - InvalidInteger { source: core::num::ParseIntError, input: String }, + ParseInt(ParseIntError), // unit implied by outer type // we use i64 to have nicer messages for negative values Conversion(i64), @@ -310,8 +310,8 @@ enum ParseError { internals::impl_from_infallible!(ParseError); impl ParseError { - fn invalid_int>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self { - move |source| Self::InvalidInteger { source, input: s.into() } + fn invalid_int>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self { + move |source| Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true , source }) } fn display( @@ -326,14 +326,16 @@ impl ParseError { use ParseError::*; match self { - InvalidInteger { source, input } if *source.kind() == IntErrorKind::PosOverflow => { - write!(f, "{} {} is above limit {}", subject, input, upper_bound) + ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) if *source.kind() == IntErrorKind::PosOverflow => { + // Outputs "failed to parse as absolute Height/Time ( is above limit )" + write!(f, "{} ({} is above limit {})", input.display_cannot_parse("absolute Height/Time"), subject, upper_bound) } - InvalidInteger { source, input } if *source.kind() == IntErrorKind::NegOverflow => { - write!(f, "{} {} is below limit {}", subject, input, lower_bound) + ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) if *source.kind() == IntErrorKind::NegOverflow => { + // Outputs "failed to parse as absolute Height/Time ( is below limit )" + write!(f, "{} ({} is below limit {})", input.display_cannot_parse("absolute Height/Time"), subject, lower_bound) } - InvalidInteger { source, input } => { - write_err!(f, "failed to parse {} as {}", input, subject; source) + ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => { + write!(f, "{} ({})", input.display_cannot_parse("absolute Height/Time"), subject) } Conversion(value) if *value < i64::from(lower_bound) => { write!(f, "{} {} is below limit {}", subject, value, lower_bound) @@ -352,20 +354,14 @@ impl ParseError { use ParseError::*; match self { - InvalidInteger { source, .. } if *source.kind() == IntErrorKind::PosOverflow => None, - InvalidInteger { source, .. } if *source.kind() == IntErrorKind::NegOverflow => None, - InvalidInteger { source, .. } => Some(source), + ParseInt(ParseIntError { source, .. }) if *source.kind() == IntErrorKind::PosOverflow => None, + ParseInt(ParseIntError { source, .. }) if *source.kind() == IntErrorKind::NegOverflow => None, + ParseInt(ParseIntError { source, .. }) => Some(source), Conversion(_) => None, } } } -impl From for ParseError { - fn from(value: ParseIntError) -> Self { - Self::InvalidInteger { source: value.source, input: value.input } - } -} - impl From for ParseError { fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) } } diff --git a/units/src/parse.rs b/units/src/parse.rs index 1ba8fbb3a..a2ee0e4db 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -7,6 +7,7 @@ use core::fmt; use core::str::FromStr; use internals::write_err; +use internals::error::InputString; /// Error with rich context returned when a string can't be parsed as an integer. /// @@ -20,26 +21,20 @@ use internals::write_err; #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct ParseIntError { - pub(crate) input: String, + pub(crate) input: InputString, // for displaying - see Display impl with nice error message below - bits: u8, + 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. - is_signed: bool, + pub(crate) is_signed: bool, pub(crate) source: core::num::ParseIntError, } -impl ParseIntError { - /// Returns the input that was attempted to be parsed. - pub fn input(&self) -> &str { &self.input } -} - impl fmt::Display for ParseIntError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let signed = if self.is_signed { "signed" } else { "unsigned" }; - let n = if self.bits == 8 { "n" } else { "" }; - write_err!(f, "failed to parse '{}' as a{} {}-bit {} integer", self.input, n, self.bits, signed; self.source) + write_err!(f, "{} ({}, {}-bit)", self.input.display_cannot_parse("integer"), signed, self.bits; self.source) } } @@ -74,7 +69,7 @@ impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128); /// /// 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 { +pub fn int + Into>(s: S) -> Result { s.as_ref().parse().map_err(|error| { ParseIntError { input: s.into(),