From 28d83551eb1db4fe34a7eb2d86aab5aa51266264 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Mon, 19 Feb 2024 14:01:11 +0100 Subject: [PATCH] Improve `ParseAmountError::InputTooLarge` This variant lacked pretty important information: what is the limit. We could just write it in the error message but it's even better to move it to a separate struct which also says how many characters exceed the limit - this helps users know how many need to be deleted. --- units/src/amount.rs | 48 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/units/src/amount.rs b/units/src/amount.rs index 61b20467..b4ee153a 100644 --- a/units/src/amount.rs +++ b/units/src/amount.rs @@ -177,6 +177,10 @@ impl From for ParseError { fn from(e: MissingDigitsError) -> Self { Self::Amount(e.into()) } } +impl From for ParseError { + fn from(e: InputTooLargeError) -> Self { Self::Amount(e.into()) } +} + impl From for ParseError { fn from(e: InvalidCharacterError) -> Self { Self::Amount(e.into()) } } @@ -217,7 +221,7 @@ pub enum ParseAmountError { /// A digit was expected but not found. MissingDigits(MissingDigitsError), /// Input string was too large. - InputTooLarge, + InputTooLarge(InputTooLargeError), /// Invalid character in input. InvalidCharacter(InvalidCharacterError), } @@ -228,6 +232,13 @@ impl From for ParseAmountError { } } +impl From for ParseAmountError { + fn from(value: InputTooLargeError) -> Self { + Self::InputTooLarge(value) + } +} + + impl From for ParseAmountError { fn from(value: InvalidCharacterError) -> Self { Self::InvalidCharacter(value) @@ -242,7 +253,7 @@ impl fmt::Display for ParseAmountError { OutOfRange(ref error) => write_err!(f, "amount out of range"; error), TooPrecise => f.write_str("amount has a too high precision"), MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error), - InputTooLarge => f.write_str("input string was too large"), + InputTooLarge(ref error) => write_err!(f, "the input is too large"; error), InvalidCharacter(ref error) => write_err!(f, "invalid character in the input"; error), } } @@ -254,7 +265,8 @@ impl std::error::Error for ParseAmountError { use ParseAmountError::*; match *self { - TooPrecise | InputTooLarge => None, + TooPrecise => None, + InputTooLarge(ref error) => Some(error), OutOfRange(ref error) => Some(error), MissingDigits(ref error) => Some(error), InvalidCharacter(ref error) => Some(error), @@ -333,6 +345,24 @@ impl From for ParseAmountError { } } +/// Error returned when the input string is too large. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct InputTooLargeError { + len: usize, +} + +impl fmt::Display for InputTooLargeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.len - INPUT_STRING_LEN_LIMIT { + 1 => write!(f, "the input is one character longer than the maximum allowed length ({})", INPUT_STRING_LEN_LIMIT), + n => write!(f, "the input is {} characters longer than the maximum allowed length ({})", n, INPUT_STRING_LEN_LIMIT), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InputTooLargeError {} + /// Error returned when digits were expected in the input but there were none. /// /// In particular, this is currently returned when the string is empty or only contains the minus sign. @@ -457,6 +487,8 @@ fn is_too_precise(s: &str, precision: usize) -> bool { } } +const INPUT_STRING_LEN_LIMIT: usize = 50; + /// Parse decimal string in the given denomination into a satoshi value and a /// bool indicator for a negative amount. fn parse_signed_to_satoshi( @@ -466,8 +498,8 @@ fn parse_signed_to_satoshi( if s.is_empty() { return Err(InnerParseError::MissingDigits(MissingDigitsError { kind: MissingDigitsKind::Empty })); } - if s.len() > 50 { - return Err(InnerParseError::InputTooLarge); + if s.len() > INPUT_STRING_LEN_LIMIT { + return Err(InnerParseError::InputTooLarge(s.len())); } let is_negative = s.starts_with('-'); @@ -547,7 +579,7 @@ enum InnerParseError { Overflow { is_negative: bool }, TooPrecise, MissingDigits(MissingDigitsError), - InputTooLarge, + InputTooLarge(usize), InvalidCharacter(InvalidCharacterError), } @@ -557,7 +589,7 @@ impl InnerParseError { Self::Overflow { is_negative } => OutOfRangeError { is_signed, is_greater_than_max: !is_negative }.into(), Self::TooPrecise => ParseAmountError::TooPrecise, Self::MissingDigits(error) => ParseAmountError::MissingDigits(error), - Self::InputTooLarge => ParseAmountError::InputTooLarge, + Self::InputTooLarge(len) => ParseAmountError::InputTooLarge(InputTooLargeError { len }), Self::InvalidCharacter(error) => ParseAmountError::InvalidCharacter(error), } } @@ -2177,7 +2209,7 @@ mod tests { // more than 50 chars. assert_eq!( p("100000000000000.00000000000000000000000000000000000", Denomination::Bitcoin), - Err(E::InputTooLarge) + Err(E::InputTooLarge(InputTooLargeError { len: 51 })) ); }