Merge rust-bitcoin/rust-bitcoin#1521: Make space optional in amount with denomination
16c49df688
Accept amounts with denominations with and without spaces (Casey Rodarmor) Pull request description: I didn't add tests for parsing with no space, but wanted to get a PR up to show the approach. Fixes #1519. ACKs for top commit: apoelstra: ACK16c49df688
Kixunil: ACK16c49df688
Tree-SHA512: 651f12974a23b711a421005cc5905cb613bfdb092b03f7b0a0e2c02b3f9351f81eb985f848d313a17506e4960df7c01b40f673a100e4230a7045364acc4865de
This commit is contained in:
commit
dd2091ed93
|
@ -288,6 +288,16 @@ fn parse_signed_to_satoshi(
|
|||
Ok((is_negative, value))
|
||||
}
|
||||
|
||||
fn split_amount_and_denomination(s: &str) -> Result<(&str, Denomination), ParseAmountError> {
|
||||
let (i, j) = if let Some(i) = s.find(' ') {
|
||||
(i, i + 1)
|
||||
} else {
|
||||
let i = s.find(|c: char| c.is_alphabetic()).ok_or(ParseAmountError::InvalidFormat)?;
|
||||
(i, i)
|
||||
};
|
||||
Ok((&s[..i], s[j..].parse()?))
|
||||
}
|
||||
|
||||
/// Options given by `fmt::Formatter`
|
||||
struct FormatOptions {
|
||||
fill: char,
|
||||
|
@ -523,14 +533,8 @@ impl Amount {
|
|||
/// If you want to parse only the amount without the denomination,
|
||||
/// use [Self::from_str_in].
|
||||
pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseAmountError> {
|
||||
let mut split = s.splitn(3, ' ');
|
||||
let amt_str = split.next().unwrap();
|
||||
let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
|
||||
if split.next().is_some() {
|
||||
return Err(ParseAmountError::InvalidFormat);
|
||||
}
|
||||
|
||||
Amount::from_str_in(amt_str, denom_str.parse()?)
|
||||
let (amt, denom) = split_amount_and_denomination(s)?;
|
||||
Amount::from_str_in(amt, denom)
|
||||
}
|
||||
|
||||
/// Express this [Amount] as a floating-point value in the given denomination.
|
||||
|
@ -864,14 +868,8 @@ impl SignedAmount {
|
|||
/// If you want to parse only the amount without the denomination,
|
||||
/// use [Self::from_str_in].
|
||||
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseAmountError> {
|
||||
let mut split = s.splitn(3, ' ');
|
||||
let amt_str = split.next().unwrap();
|
||||
let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
|
||||
if split.next().is_some() {
|
||||
return Err(ParseAmountError::InvalidFormat);
|
||||
}
|
||||
|
||||
SignedAmount::from_str_in(amt_str, denom_str.parse()?)
|
||||
let (amt, denom) = split_amount_and_denomination(s)?;
|
||||
SignedAmount::from_str_in(amt, denom)
|
||||
}
|
||||
|
||||
/// Express this [SignedAmount] as a floating-point value in the given denomination.
|
||||
|
@ -1896,39 +1894,51 @@ mod tests {
|
|||
#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin.
|
||||
fn from_str() {
|
||||
use super::ParseAmountError as E;
|
||||
let p = Amount::from_str;
|
||||
let sp = SignedAmount::from_str;
|
||||
|
||||
assert_eq!(p("x BTC"), Err(E::InvalidCharacter('x')));
|
||||
assert_eq!(p("5 BTC BTC"), Err(E::InvalidFormat));
|
||||
assert_eq!(p("5 5 BTC"), Err(E::InvalidFormat));
|
||||
assert_eq!(Amount::from_str("x BTC"), Err(E::InvalidCharacter('x')));
|
||||
assert_eq!(Amount::from_str("xBTC"), Err(E::UnknownDenomination("xBTC".into())));
|
||||
assert_eq!(Amount::from_str("5 BTC BTC"), Err(E::UnknownDenomination("BTC BTC".into())));
|
||||
assert_eq!(Amount::from_str("5BTC BTC"), Err(E::InvalidCharacter('B')));
|
||||
assert_eq!(Amount::from_str("5 5 BTC"), Err(E::UnknownDenomination("5 BTC".into())));
|
||||
|
||||
assert_eq!(p("5 BCH"), Err(E::UnknownDenomination("BCH".to_owned())));
|
||||
#[cfg_attr(rust_v_1_46, track_caller)]
|
||||
fn case(s: &str, expected: Result<Amount, ParseAmountError>) {
|
||||
assert_eq!(Amount::from_str(s), expected);
|
||||
assert_eq!(Amount::from_str(&s.replace(' ', "")), expected);
|
||||
}
|
||||
|
||||
assert_eq!(p("-1 BTC"), Err(E::Negative));
|
||||
assert_eq!(p("-0.0 BTC"), Err(E::Negative));
|
||||
assert_eq!(p("0.123456789 BTC"), Err(E::TooPrecise));
|
||||
assert_eq!(sp("-0.1 satoshi"), Err(E::TooPrecise));
|
||||
assert_eq!(p("0.123456 mBTC"), Err(E::TooPrecise));
|
||||
assert_eq!(sp("-1.001 bits"), Err(E::TooPrecise));
|
||||
assert_eq!(sp("-200000000000 BTC"), Err(E::TooBig));
|
||||
assert_eq!(p("18446744073709551616 sat"), Err(E::TooBig));
|
||||
#[cfg_attr(rust_v_1_46, track_caller)]
|
||||
fn scase(s: &str, expected: Result<SignedAmount, ParseAmountError>) {
|
||||
assert_eq!(SignedAmount::from_str(s), expected);
|
||||
assert_eq!(SignedAmount::from_str(&s.replace(' ', "")), expected);
|
||||
}
|
||||
|
||||
assert_eq!(sp("0 msat"), Err(E::TooPrecise));
|
||||
assert_eq!(sp("-0 msat"), Err(E::TooPrecise));
|
||||
assert_eq!(sp("000 msat"), Err(E::TooPrecise));
|
||||
assert_eq!(sp("-000 msat"), Err(E::TooPrecise));
|
||||
assert_eq!(p("0 msat"), Err(E::TooPrecise));
|
||||
assert_eq!(p("-0 msat"), Err(E::TooPrecise));
|
||||
assert_eq!(p("000 msat"), Err(E::TooPrecise));
|
||||
assert_eq!(p("-000 msat"), Err(E::TooPrecise));
|
||||
case("5 BCH", Err(E::UnknownDenomination("BCH".to_owned())));
|
||||
|
||||
assert_eq!(p(".5 bits"), Ok(Amount::from_sat(50)));
|
||||
assert_eq!(sp("-.5 bits"), Ok(SignedAmount::from_sat(-50)));
|
||||
assert_eq!(p("0.00253583 BTC"), Ok(Amount::from_sat(253583)));
|
||||
assert_eq!(sp("-5 satoshi"), Ok(SignedAmount::from_sat(-5)));
|
||||
assert_eq!(p("0.10000000 BTC"), Ok(Amount::from_sat(100_000_00)));
|
||||
assert_eq!(sp("-100 bits"), Ok(SignedAmount::from_sat(-10_000)));
|
||||
case("-1 BTC", Err(E::Negative));
|
||||
case("-0.0 BTC", Err(E::Negative));
|
||||
case("0.123456789 BTC", Err(E::TooPrecise));
|
||||
scase("-0.1 satoshi", Err(E::TooPrecise));
|
||||
case("0.123456 mBTC", Err(E::TooPrecise));
|
||||
scase("-1.001 bits", Err(E::TooPrecise));
|
||||
scase("-200000000000 BTC", Err(E::TooBig));
|
||||
case("18446744073709551616 sat", Err(E::TooBig));
|
||||
|
||||
scase("0 msat", Err(E::TooPrecise));
|
||||
scase("-0 msat", Err(E::TooPrecise));
|
||||
scase("000 msat", Err(E::TooPrecise));
|
||||
scase("-000 msat", Err(E::TooPrecise));
|
||||
case("0 msat", Err(E::TooPrecise));
|
||||
case("-0 msat", Err(E::TooPrecise));
|
||||
case("000 msat", Err(E::TooPrecise));
|
||||
case("-000 msat", Err(E::TooPrecise));
|
||||
|
||||
case(".5 bits", Ok(Amount::from_sat(50)));
|
||||
scase("-.5 bits", Ok(SignedAmount::from_sat(-50)));
|
||||
case("0.00253583 BTC", Ok(Amount::from_sat(253583)));
|
||||
scase("-5 satoshi", Ok(SignedAmount::from_sat(-5)));
|
||||
case("0.10000000 BTC", Ok(Amount::from_sat(100_000_00)));
|
||||
scase("-100 bits", Ok(SignedAmount::from_sat(-10_000)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2034,8 +2044,14 @@ mod tests {
|
|||
assert_eq!(Amount::from_str(&denom(amt, D::MilliSatoshi)), Ok(amt));
|
||||
assert_eq!(Amount::from_str(&denom(amt, D::PicoBitcoin)), Ok(amt));
|
||||
|
||||
assert_eq!(Amount::from_str("42 satoshi BTC"), Err(ParseAmountError::InvalidFormat));
|
||||
assert_eq!(SignedAmount::from_str("-42 satoshi BTC"), Err(ParseAmountError::InvalidFormat));
|
||||
assert_eq!(
|
||||
Amount::from_str("42 satoshi BTC"),
|
||||
Err(ParseAmountError::UnknownDenomination("satoshi BTC".into())),
|
||||
);
|
||||
assert_eq!(
|
||||
SignedAmount::from_str("-42 satoshi BTC"),
|
||||
Err(ParseAmountError::UnknownDenomination("satoshi BTC".into())),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
Loading…
Reference in New Issue