use core::convert::Infallible;
use core::fmt;
use internals::error::InputString;
use internals::write_err;
use super::INPUT_STRING_LEN_LIMIT;
use crate::parse_int::{PrefixedHexError, UnprefixedHexError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError(pub(crate) ParseErrorInner);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ParseErrorInner {
Amount(ParseAmountError),
Denomination(ParseDenominationError),
MissingDenomination(MissingDenominationError),
}
impl From<Infallible> for ParseError {
fn from(never: Infallible) -> Self { match never {} }
}
impl From<Infallible> for ParseErrorInner {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ParseErrorInner::Amount(ref e) => write_err!(f, "invalid amount"; e),
ParseErrorInner::Denomination(ref e) => write_err!(f, "invalid denomination"; e),
ParseErrorInner::MissingDenomination(ref e) => write_err!(f, "missing denomination"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self.0 {
ParseErrorInner::Amount(ref e) => Some(e),
ParseErrorInner::Denomination(ref e) => Some(e),
ParseErrorInner::MissingDenomination(ref e) => Some(e),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseAmountError(pub(crate) ParseAmountErrorInner);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ParseAmountErrorInner {
OutOfRange(OutOfRangeError),
TooPrecise(TooPreciseError),
MissingDigits(MissingDigitsError),
InputTooLarge(InputTooLargeError),
InvalidCharacter(InvalidCharacterError),
BadPosition(BadPositionError),
PrefixedHex(PrefixedHexError),
UnprefixedHex(UnprefixedHexError),
}
impl From<Infallible> for ParseAmountError {
fn from(never: Infallible) -> Self { match never {} }
}
impl From<Infallible> for ParseAmountErrorInner {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for ParseAmountError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ParseAmountErrorInner as E;
match self.0 {
E::OutOfRange(ref error) => write_err!(f, "amount out of range"; error),
E::TooPrecise(ref error) => write_err!(f, "amount has a too high precision"; error),
E::MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error),
E::InputTooLarge(ref error) => write_err!(f, "the input is too large"; error),
E::InvalidCharacter(ref error) => {
write_err!(f, "invalid character in the input"; error)
}
E::BadPosition(ref error) => write_err!(f, "valid character in bad position"; error),
E::PrefixedHex(ref error) => write_err!(f, "prefixed hex is invalid"; error),
E::UnprefixedHex(ref error) => write_err!(f, "unprefixed hex is invalid"; error),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseAmountError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use ParseAmountErrorInner as E;
match self.0 {
E::TooPrecise(ref error) => Some(error),
E::InputTooLarge(ref error) => Some(error),
E::OutOfRange(ref error) => Some(error),
E::MissingDigits(ref error) => Some(error),
E::InvalidCharacter(ref error) => Some(error),
E::BadPosition(ref error) => Some(error),
E::PrefixedHex(ref error) => Some(error),
E::UnprefixedHex(ref error) => Some(error),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct OutOfRangeError {
pub(super) is_signed: bool,
pub(super) is_greater_than_max: bool,
}
impl OutOfRangeError {
#[inline]
pub fn valid_range(self) -> (i64, u64) {
match self.is_signed {
true => (i64::MIN, i64::MAX as u64),
false => (0, u64::MAX),
}
}
#[inline]
pub fn is_above_max(self) -> bool { self.is_greater_than_max }
#[inline]
pub fn is_below_min(self) -> bool { !self.is_greater_than_max }
#[cfg(test)]
#[inline]
pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
#[cfg(test)]
#[inline]
pub(crate) fn too_small() -> Self {
Self {
is_signed: true,
is_greater_than_max: false,
}
}
#[inline]
pub(crate) fn negative() -> Self {
Self {
is_signed: false,
is_greater_than_max: false,
}
}
}
impl From<Infallible> for OutOfRangeError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for OutOfRangeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_greater_than_max {
write!(f, "the amount is greater than {}", self.valid_range().1)
} else {
write!(f, "the amount is less than {}", self.valid_range().0)
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for OutOfRangeError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TooPreciseError {
pub(super) position: usize,
}
impl From<Infallible> for TooPreciseError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for TooPreciseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.position {
0 => f.write_str("the amount is less than 1 satoshi but it's not zero"),
pos => write!(
f,
"the digits starting from position {} represent a sub-satoshi amount",
pos
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for TooPreciseError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InputTooLargeError {
pub(super) len: usize,
}
impl From<Infallible> for InputTooLargeError {
fn from(never: Infallible) -> Self { match never {} }
}
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 {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MissingDigitsError {
pub(super) kind: MissingDigitsKind,
}
impl From<Infallible> for MissingDigitsError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for MissingDigitsError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
MissingDigitsKind::Empty => f.write_str("the input is empty"),
MissingDigitsKind::OnlyMinusSign =>
f.write_str("there are no digits following the minus (-) sign"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for MissingDigitsError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(super) enum MissingDigitsKind {
Empty,
OnlyMinusSign,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidCharacterError {
pub(super) invalid_char: char,
pub(super) position: usize,
}
impl From<Infallible> for InvalidCharacterError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for InvalidCharacterError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.invalid_char {
'.' => f.write_str("there is more than one decimal separator (dot) in the input"),
'-' => f.write_str("there is more than one minus sign (-) in the input"),
c => write!(
f,
"the character '{}' at position {} is not a valid digit",
c, self.position
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidCharacterError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BadPositionError {
pub(super) char: char,
pub(super) position: usize,
}
impl From<Infallible> for BadPositionError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for BadPositionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.char {
'_' => match self.position {
0 => f.write_str("the input amount is prefixed with an underscore (_)"),
1 => f.write_str("the input amount is prefixed with an underscore (_)"),
_ => f.write_str("there are consecutive underscores (_) in the input"),
},
c => write!(f, "The character '{}' is at a bad position: {}", c, self.position),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for BadPositionError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseDenominationError {
Unknown(UnknownDenominationError),
PossiblyConfusing(PossiblyConfusingDenominationError),
}
impl From<Infallible> for ParseDenominationError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for ParseDenominationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Unknown(ref e) => write_err!(f, "denomination parse error"; e),
Self::PossiblyConfusing(ref e) => write_err!(f, "denomination parse error"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseDenominationError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::Unknown(ref e) => Some(e),
Self::PossiblyConfusing(ref e) => Some(e),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct MissingDenominationError;
impl From<Infallible> for MissingDenominationError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for MissingDenominationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "the input does not contain a denomination")
}
}
#[cfg(feature = "std")]
impl std::error::Error for MissingDenominationError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnknownDenominationError(pub(super) InputString);
impl From<Infallible> for UnknownDenominationError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for UnknownDenominationError {
#[inline]
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 {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PossiblyConfusingDenominationError(pub(super) InputString);
impl From<Infallible> for PossiblyConfusingDenominationError {
fn from(never: Infallible) -> Self { match never {} }
}
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 {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[cfg(feature = "encoding")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AmountDecoderError(pub(super) AmountDecoderErrorInner);
#[cfg(feature = "encoding")]
impl AmountDecoderError {
#[inline]
pub(super) fn eof(e: encoding::UnexpectedEofError) -> Self {
Self(AmountDecoderErrorInner::UnexpectedEof(e))
}
#[inline]
pub(super) fn out_of_range(e: OutOfRangeError) -> Self {
Self(AmountDecoderErrorInner::OutOfRange(e))
}
}
#[cfg(feature = "encoding")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum AmountDecoderErrorInner {
UnexpectedEof(encoding::UnexpectedEofError),
OutOfRange(OutOfRangeError),
}
#[cfg(feature = "encoding")]
impl From<Infallible> for AmountDecoderError {
fn from(never: Infallible) -> Self { match never {} }
}
#[cfg(feature = "encoding")]
impl fmt::Display for AmountDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use AmountDecoderErrorInner as E;
match self.0 {
E::UnexpectedEof(ref e) => write_err!(f, "decode error"; e),
E::OutOfRange(ref e) => write_err!(f, "decode error"; e),
}
}
}
#[cfg(feature = "encoding")]
#[cfg(feature = "std")]
impl std::error::Error for AmountDecoderError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use AmountDecoderErrorInner as E;
match self.0 {
E::UnexpectedEof(ref e) => Some(e),
E::OutOfRange(ref e) => Some(e),
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::string::ToString;
#[cfg(feature = "alloc")]
use core::str::FromStr;
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "encoding")]
use encoding::{Decode as _, Decoder as _};
#[cfg(feature = "alloc")]
use super::{ParseAmountError, ParseAmountErrorInner, ParseErrorInner};
#[cfg(feature = "alloc")]
use crate::amount::{Amount, Denomination, ParseDenominationError, ParseError};
#[test]
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_lines)] fn error_display_is_non_empty() {
macro_rules! assert_amount_err {
($e:expr, $enum_arm:ident, $err_msg:expr) => {
assert!(!$e.to_string().is_empty());
let ParseError(ParseErrorInner::Amount(err)) = $e else { panic!($err_msg) };
assert!(!err.to_string().is_empty());
#[cfg(feature = "std")]
assert!(err.source().is_some());
let ParseAmountError(ParseAmountErrorInner::$enum_arm(err)) = err else {
panic!($err_msg)
};
assert!(!err.to_string().is_empty());
#[cfg(feature = "std")]
assert!(err.source().is_none());
};
}
let long_input = alloc::format!("{} BTC", "1".repeat(51));
let e = Amount::from_str(&long_input).unwrap_err();
assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
let long_input = alloc::format!("{} BTC", "1".repeat(52));
let e = Amount::from_str(&long_input).unwrap_err();
assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
let e = Amount::from_str("12x34 BTC").unwrap_err();
assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
let e = Amount::from_str("12.3.4 BTC").unwrap_err();
assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
let e = Amount::from_str("--1234 BTC").unwrap_err();
assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
let e = Amount::from_str("BTC").unwrap_err();
assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
let e = Amount::from_str("- BTC").unwrap_err();
assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
let e = Amount::from_str("21000001 BTC").unwrap_err();
assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
let e = Amount::from_str("-10 BTC").unwrap_err();
assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
let e = Amount::from_str("0.000000001 BTC").unwrap_err();
assert_amount_err!(e, TooPrecise, "error should be TooPreciseError");
let e = Amount::from_str("_123 BTC").unwrap_err();
assert_amount_err!(e, BadPosition, "error should be BadPositionError");
let e = Amount::from_str("-_123 BTC").unwrap_err();
assert_amount_err!(e, BadPosition, "error should be BadPositionError");
let e = Amount::from_str("1__23 BTC").unwrap_err();
assert_amount_err!(e, BadPosition, "error should be BadPositionError");
let e = Amount::from_str_in("invalid", Denomination::Bitcoin).unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
let e = Denomination::from_str("XYZ").unwrap_err();
#[cfg(feature = "std")]
assert!(e.source().is_some());
let ParseDenominationError::Unknown(e) = e else {
panic!("error should be UnknownDenominationError")
};
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_none());
let e = Denomination::from_str("MBTC").unwrap_err();
#[cfg(feature = "std")]
assert!(e.source().is_some());
let ParseDenominationError::PossiblyConfusing(e) = e else {
panic!("error should be PossiblyConfusingDenominationError")
};
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_none());
let e = Denomination::from_str("UNKNOWN").unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
let e = Denomination::from_str("MBTC").unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
let e = "invalid BTC".parse::<Amount>().unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
let e = "123 GBTC".parse::<Amount>().unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
let e = "123".parse::<Amount>().unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
#[cfg(feature = "encoding")]
{
let mut decoder = Amount::decoder();
let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
let e = decoder.end().unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
let mut decoder = Amount::decoder();
let _ =
decoder.push_bytes(&mut (21_000_001 * 100_000_000_u64).to_le_bytes().as_slice());
let e = decoder.end().unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
}
}
}