Use InputString instead of String
Done so that we can correctly support builds without an allocator. Add two new error types that wrap `InputString`.
This commit is contained in:
parent
acacf45edf
commit
396e049a7a
|
@ -14,8 +14,8 @@ exclude = ["tests", "contrib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = ["alloc"]
|
std = ["alloc", "internals/std"]
|
||||||
alloc = []
|
alloc = ["internals/alloc"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
|
@ -12,6 +12,7 @@ use core::{default, ops};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use ::serde::{Deserialize, Serialize};
|
use ::serde::{Deserialize, Serialize};
|
||||||
|
use internals::error::InputString;
|
||||||
use internals::write_err;
|
use internals::write_err;
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
|
@ -129,12 +130,12 @@ impl FromStr for Denomination {
|
||||||
use self::ParseDenominationError::*;
|
use self::ParseDenominationError::*;
|
||||||
|
|
||||||
if CONFUSING_FORMS.contains(&s) {
|
if CONFUSING_FORMS.contains(&s) {
|
||||||
return Err(PossiblyConfusing(s.to_string()));
|
return Err(PossiblyConfusing(PossiblyConfusingDenominationError(s.into())));
|
||||||
};
|
};
|
||||||
|
|
||||||
let form = self::Denomination::forms(s);
|
let form = self::Denomination::forms(s);
|
||||||
|
|
||||||
form.ok_or_else(|| Unknown(s.to_string()))
|
form.ok_or_else(|| Unknown(UnknownDenominationError(s.into())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,9 +197,9 @@ impl From<ParseDenominationError> for ParseAmountError {
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum ParseDenominationError {
|
pub enum ParseDenominationError {
|
||||||
/// The denomination was unknown.
|
/// The denomination was unknown.
|
||||||
Unknown(String),
|
Unknown(UnknownDenominationError),
|
||||||
/// The denomination has multiple possible interpretations.
|
/// The denomination has multiple possible interpretations.
|
||||||
PossiblyConfusing(String),
|
PossiblyConfusing(PossiblyConfusingDenominationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ParseDenominationError {
|
impl fmt::Display for ParseDenominationError {
|
||||||
|
@ -206,16 +207,8 @@ impl fmt::Display for ParseDenominationError {
|
||||||
use ParseDenominationError::*;
|
use ParseDenominationError::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
Unknown(ref d) => write!(f, "unknown denomination: {}", d),
|
Unknown(ref e) => write_err!(f, "denomination parse error"; e),
|
||||||
PossiblyConfusing(ref d) => {
|
PossiblyConfusing(ref e) => write_err!(f, "denomination parse error"; e),
|
||||||
let (letter, upper, lower) = match d.chars().next() {
|
|
||||||
Some('M') => ('M', "Mega", "milli"),
|
|
||||||
Some('P') => ('P', "Peta", "pico"),
|
|
||||||
// This panic could be avoided by adding enum ConfusingDenomination { Mega, Peta } but is it worth it?
|
|
||||||
_ => panic!("invalid error information"),
|
|
||||||
};
|
|
||||||
write!(f, "the '{}' at the beginning of {} should technically mean '{}' but that denomination is uncommon and maybe '{}' was intended", letter, d, upper, lower)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,6 +224,38 @@ impl std::error::Error for ParseDenominationError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parsing error, unknown denomination.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct UnknownDenominationError(InputString);
|
||||||
|
|
||||||
|
impl fmt::Display for UnknownDenominationError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.0.unknown_variant("bitcoin denomination", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for UnknownDenominationError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsing error, possibly confusing denomination.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct PossiblyConfusingDenominationError(InputString);
|
||||||
|
|
||||||
|
impl fmt::Display for PossiblyConfusingDenominationError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}: possibly confusing denomination - we intentionally do not support 'M' and 'P' so as to not confuse mega/milli and peta/pico", self.0.display_cannot_parse("bitcoin denomination"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for PossiblyConfusingDenominationError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
||||||
|
}
|
||||||
|
|
||||||
fn is_too_precise(s: &str, precision: usize) -> bool {
|
fn is_too_precise(s: &str, precision: usize) -> bool {
|
||||||
match s.find('.') {
|
match s.find('.') {
|
||||||
Some(pos) =>
|
Some(pos) =>
|
||||||
|
@ -1994,21 +2019,23 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[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 ParseDenominationError::*;
|
||||||
|
|
||||||
use super::ParseAmountError as E;
|
use super::ParseAmountError as E;
|
||||||
|
|
||||||
assert_eq!(Amount::from_str("x BTC"), Err(E::InvalidCharacter('x')));
|
assert_eq!(Amount::from_str("x BTC"), Err(E::InvalidCharacter('x')));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::from_str("xBTC"),
|
Amount::from_str("xBTC"),
|
||||||
Err(ParseDenominationError::Unknown("xBTC".into()).into())
|
Err(Unknown(UnknownDenominationError("xBTC".into())).into()),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::from_str("5 BTC BTC"),
|
Amount::from_str("5 BTC BTC"),
|
||||||
Err(ParseDenominationError::Unknown("BTC BTC".into()).into())
|
Err(Unknown(UnknownDenominationError("BTC BTC".into())).into()),
|
||||||
);
|
);
|
||||||
assert_eq!(Amount::from_str("5BTC BTC"), Err(E::InvalidCharacter('B')));
|
assert_eq!(Amount::from_str("5BTC BTC"), Err(E::InvalidCharacter('B')));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::from_str("5 5 BTC"),
|
Amount::from_str("5 5 BTC"),
|
||||||
Err(ParseDenominationError::Unknown("5 BTC".into()).into())
|
Err(Unknown(UnknownDenominationError("5 BTC".into())).into()),
|
||||||
);
|
);
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -2023,7 +2050,7 @@ mod tests {
|
||||||
assert_eq!(SignedAmount::from_str(&s.replace(' ', "")), expected);
|
assert_eq!(SignedAmount::from_str(&s.replace(' ', "")), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
case("5 BCH", Err(ParseDenominationError::Unknown("BCH".into()).into()));
|
case("5 BCH", Err(Unknown(UnknownDenominationError("BCH".into())).into()));
|
||||||
|
|
||||||
case("-1 BTC", Err(E::Negative));
|
case("-1 BTC", Err(E::Negative));
|
||||||
case("-0.0 BTC", Err(E::Negative));
|
case("-0.0 BTC", Err(E::Negative));
|
||||||
|
@ -2133,7 +2160,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_string_with_denomination_from_str_roundtrip() {
|
fn to_string_with_denomination_from_str_roundtrip() {
|
||||||
|
use ParseDenominationError::*;
|
||||||
|
|
||||||
use super::Denomination as D;
|
use super::Denomination as D;
|
||||||
|
|
||||||
let amt = Amount::from_sat(42);
|
let amt = Amount::from_sat(42);
|
||||||
let denom = Amount::to_string_with_denomination;
|
let denom = Amount::to_string_with_denomination;
|
||||||
assert_eq!(Amount::from_str(&denom(amt, D::Bitcoin)), Ok(amt));
|
assert_eq!(Amount::from_str(&denom(amt, D::Bitcoin)), Ok(amt));
|
||||||
|
@ -2147,11 +2177,11 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Amount::from_str("42 satoshi BTC"),
|
Amount::from_str("42 satoshi BTC"),
|
||||||
Err(ParseDenominationError::Unknown("satoshi BTC".to_owned()).into()),
|
Err(Unknown(UnknownDenominationError("satoshi BTC".into())).into()),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SignedAmount::from_str("-42 satoshi BTC"),
|
SignedAmount::from_str("-42 satoshi BTC"),
|
||||||
Err(ParseDenominationError::Unknown("satoshi BTC".to_owned()).into()),
|
Err(Unknown(UnknownDenominationError("satoshi BTC".into())).into()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue