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))
|
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`
|
/// Options given by `fmt::Formatter`
|
||||||
struct FormatOptions {
|
struct FormatOptions {
|
||||||
fill: char,
|
fill: char,
|
||||||
|
@ -523,14 +533,8 @@ impl Amount {
|
||||||
/// If you want to parse only the amount without the denomination,
|
/// If you want to parse only the amount without the denomination,
|
||||||
/// use [Self::from_str_in].
|
/// use [Self::from_str_in].
|
||||||
pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseAmountError> {
|
pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseAmountError> {
|
||||||
let mut split = s.splitn(3, ' ');
|
let (amt, denom) = split_amount_and_denomination(s)?;
|
||||||
let amt_str = split.next().unwrap();
|
Amount::from_str_in(amt, denom)
|
||||||
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()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Express this [Amount] as a floating-point value in the given denomination.
|
/// 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,
|
/// If you want to parse only the amount without the denomination,
|
||||||
/// use [Self::from_str_in].
|
/// use [Self::from_str_in].
|
||||||
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseAmountError> {
|
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseAmountError> {
|
||||||
let mut split = s.splitn(3, ' ');
|
let (amt, denom) = split_amount_and_denomination(s)?;
|
||||||
let amt_str = split.next().unwrap();
|
SignedAmount::from_str_in(amt, denom)
|
||||||
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()?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Express this [SignedAmount] as a floating-point value in the given denomination.
|
/// 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.
|
#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin.
|
||||||
fn from_str() {
|
fn from_str() {
|
||||||
use super::ParseAmountError as E;
|
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!(Amount::from_str("x BTC"), Err(E::InvalidCharacter('x')));
|
||||||
assert_eq!(p("5 BTC BTC"), Err(E::InvalidFormat));
|
assert_eq!(Amount::from_str("xBTC"), Err(E::UnknownDenomination("xBTC".into())));
|
||||||
assert_eq!(p("5 5 BTC"), Err(E::InvalidFormat));
|
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));
|
#[cfg_attr(rust_v_1_46, track_caller)]
|
||||||
assert_eq!(p("-0.0 BTC"), Err(E::Negative));
|
fn scase(s: &str, expected: Result<SignedAmount, ParseAmountError>) {
|
||||||
assert_eq!(p("0.123456789 BTC"), Err(E::TooPrecise));
|
assert_eq!(SignedAmount::from_str(s), expected);
|
||||||
assert_eq!(sp("-0.1 satoshi"), Err(E::TooPrecise));
|
assert_eq!(SignedAmount::from_str(&s.replace(' ', "")), expected);
|
||||||
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));
|
|
||||||
|
|
||||||
assert_eq!(sp("0 msat"), Err(E::TooPrecise));
|
case("5 BCH", Err(E::UnknownDenomination("BCH".to_owned())));
|
||||||
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));
|
|
||||||
|
|
||||||
assert_eq!(p(".5 bits"), Ok(Amount::from_sat(50)));
|
case("-1 BTC", Err(E::Negative));
|
||||||
assert_eq!(sp("-.5 bits"), Ok(SignedAmount::from_sat(-50)));
|
case("-0.0 BTC", Err(E::Negative));
|
||||||
assert_eq!(p("0.00253583 BTC"), Ok(Amount::from_sat(253583)));
|
case("0.123456789 BTC", Err(E::TooPrecise));
|
||||||
assert_eq!(sp("-5 satoshi"), Ok(SignedAmount::from_sat(-5)));
|
scase("-0.1 satoshi", Err(E::TooPrecise));
|
||||||
assert_eq!(p("0.10000000 BTC"), Ok(Amount::from_sat(100_000_00)));
|
case("0.123456 mBTC", Err(E::TooPrecise));
|
||||||
assert_eq!(sp("-100 bits"), Ok(SignedAmount::from_sat(-10_000)));
|
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]
|
#[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::MilliSatoshi)), Ok(amt));
|
||||||
assert_eq!(Amount::from_str(&denom(amt, D::PicoBitcoin)), 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!(
|
||||||
assert_eq!(SignedAmount::from_str("-42 satoshi BTC"), Err(ParseAmountError::InvalidFormat));
|
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")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
Loading…
Reference in New Issue