Remove From<SubError> for Error impls
What changed, and why it matters
This commit removes automatic conversion traits (From implementations) that let sub-errors be silently turned into parent errors. It replaces them with explicit error wrapping at each call site. This is a code-quality and API-clarity change, not a security fix. There is no evidence in the commit message or diff that it addresses a vulnerability, exploit, or bug that could affect users.
No security action required. Treat as a normal refactoring/API-hardening commit. Reviewers may want to verify that all error conversions were replaced correctly and that no From impls remain that could reintroduce implicit conversions.
Security signals we found
No security-relevant keywords in commit title or message
No functional change to parsing/decoding logic observed
No bounds-check or input-validation changes observed
No references to CVE, advisory, security report, or researcher attribution in commit materials
Evidence from the diff
The patch deletes blanket From
Changed components
primitives/src/script/owned.rsprimitives/src/transaction.rsunits/src/amount/error.rsunits/src/amount/mod.rsunits/src/amount/signed.rsunits/src/amount/tests.rsunits/src/amount/unsigned.rsInspect captured patch +224 / −183
diff --git a/primitives/src/script/owned.rs b/primitives/src/script/owned.rs
index 2c17fa01..5895f018 100644
--- a/primitives/src/script/owned.rs
+++ b/primitives/src/script/owned.rs
@@ -201,11 +201,13 @@ impl<T> Decoder for ScriptBufDecoder<T> {
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- Ok(self.0.push_bytes(bytes)?)
+ Ok(self.0.push_bytes(bytes).map_err(ScriptBufDecoderError)?)
}
#[inline]
- fn end(self) -> Result<Self::Output, Self::Error> { Ok(ScriptBuf::from_bytes(self.0.end()?)) }
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ Ok(ScriptBuf::from_bytes(self.0.end().map_err(ScriptBufDecoderError)?))
+ }
#[inline]
fn read_limit(&self) -> usize { self.0.read_limit() }
@@ -224,10 +226,6 @@ impl From<Infallible> for ScriptBufDecoderError {
fn from(never: Infallible) -> Self { match never {} }
}
-impl From<ByteVecDecoderError> for ScriptBufDecoderError {
- fn from(e: ByteVecDecoderError) -> Self { Self(e) }
-}
-
impl fmt::Display for ScriptBufDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write_err!(f, "decoder error"; self.0) }
}
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index 450c5344..4029e81c 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -467,13 +467,13 @@ impl Decoder for TransactionDecoder {
// Attempt to push to the currently-active decoder and return early on success.
match &mut self.state {
State::Version(decoder) => {
- if decoder.push_bytes(bytes)? {
+ if decoder.push_bytes(bytes).map_err(|e| E(Inner::Version(e)))? {
// Still more bytes required.
return Ok(true);
}
}
State::Inputs(_, _, decoder) =>
- if decoder.push_bytes(bytes)? {
+ if decoder.push_bytes(bytes).map_err(|e| E(Inner::Inputs(e)))? {
return Ok(true);
},
State::SegwitFlag(_) =>
@@ -481,15 +481,15 @@ impl Decoder for TransactionDecoder {
return Ok(true);
},
State::Outputs(_, _, _, decoder) =>
- if decoder.push_bytes(bytes)? {
+ if decoder.push_bytes(bytes).map_err(|e| E(Inner::Outputs(e)))? {
return Ok(true);
},
State::Witnesses(_, _, _, _, decoder) =>
- if decoder.push_bytes(bytes)? {
+ if decoder.push_bytes(bytes).map_err(|e| E(Inner::Witness(e)))? {
return Ok(true);
},
State::LockTime(_, _, _, decoder) =>
- if decoder.push_bytes(bytes)? {
+ if decoder.push_bytes(bytes).map_err(|e| E(Inner::LockTime(e)))? {
return Ok(true);
},
State::Done(..) => return Ok(false),
@@ -499,11 +499,11 @@ impl Decoder for TransactionDecoder {
// If the above failed, end the current decoder and go to the next state.
match mem::replace(&mut self.state, State::Errored) {
State::Version(decoder) => {
- let version = decoder.end()?;
+ let version = decoder.end().map_err(|e| E(Inner::Version(e)))?;
self.state = State::Inputs(version, Attempt::First, VecDecoder::<TxIn>::new());
}
State::Inputs(version, attempt, decoder) => {
- let inputs = decoder.end()?;
+ let inputs = decoder.end().map_err(|e| E(Inner::Inputs(e)))?;
if Attempt::First == attempt {
if inputs.is_empty() {
@@ -535,7 +535,7 @@ impl Decoder for TransactionDecoder {
self.state = State::Inputs(version, Attempt::Second, VecDecoder::<TxIn>::new());
}
State::Outputs(version, inputs, is_segwit, decoder) => {
- let outputs = decoder.end()?;
+ let outputs = decoder.end().map_err(|e| E(Inner::Outputs(e)))?;
// Handle the zero-input case described in the `Transaction` docs.
if is_segwit == IsSegwit::Yes && !inputs.is_empty() {
self.state = State::Witnesses(
@@ -553,7 +553,7 @@ impl Decoder for TransactionDecoder {
State::Witnesses(version, mut inputs, outputs, iteration, decoder) => {
let iteration = iteration.0;
- inputs[iteration].witness = decoder.end()?;
+ inputs[iteration].witness = decoder.end().map_err(|e| E(Inner::Witness(e)))?;
if iteration < inputs.len() - 1 {
self.state = State::Witnesses(
version,
@@ -572,7 +572,7 @@ impl Decoder for TransactionDecoder {
}
}
State::LockTime(version, inputs, outputs, decoder) => {
- let lock_time = decoder.end()?;
+ let lock_time = decoder.end().map_err(|e| E(Inner::LockTime(e)))?;
self.state = State::Done(Transaction { version, lock_time, inputs, outputs });
return Ok(false);
}
@@ -758,35 +758,6 @@ impl From<Infallible> for TransactionDecoderError {
fn from(never: Infallible) -> Self { match never {} }
}
-#[cfg(feature = "alloc")]
-impl From<VersionDecoderError> for TransactionDecoderError {
- fn from(e: VersionDecoderError) -> Self { Self(TransactionDecoderErrorInner::Version(e)) }
-}
-
-#[cfg(feature = "alloc")]
-impl From<VecDecoderError<TxInDecoderError>> for TransactionDecoderError {
- fn from(e: VecDecoderError<TxInDecoderError>) -> Self {
- Self(TransactionDecoderErrorInner::Inputs(e))
- }
-}
-
-#[cfg(feature = "alloc")]
-impl From<VecDecoderError<TxOutDecoderError>> for TransactionDecoderError {
- fn from(e: VecDecoderError<TxOutDecoderError>) -> Self {
- Self(TransactionDecoderErrorInner::Outputs(e))
- }
-}
-
-#[cfg(feature = "alloc")]
-impl From<WitnessDecoderError> for TransactionDecoderError {
- fn from(e: WitnessDecoderError) -> Self { Self(TransactionDecoderErrorInner::Witness(e)) }
-}
-
-#[cfg(feature = "alloc")]
-impl From<LockTimeDecoderError> for TransactionDecoderError {
- fn from(e: LockTimeDecoderError) -> Self { Self(TransactionDecoderErrorInner::LockTime(e)) }
-}
-
#[cfg(feature = "alloc")]
impl fmt::Display for TransactionDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
diff --git a/units/src/amount/error.rs b/units/src/amount/error.rs
index cd19c5f2..87c9246a 100644
--- a/units/src/amount/error.rs
+++ b/units/src/amount/error.rs
@@ -33,38 +33,6 @@ impl From<Infallible> for ParseErrorInner {
fn from(never: Infallible) -> Self { match never {} }
}
-impl From<ParseAmountError> for ParseError {
- fn from(e: ParseAmountError) -> Self { Self(ParseErrorInner::Amount(e)) }
-}
-
-impl From<ParseDenominationError> for ParseError {
- fn from(e: ParseDenominationError) -> Self { Self(ParseErrorInner::Denomination(e)) }
-}
-
-impl From<OutOfRangeError> for ParseError {
- fn from(e: OutOfRangeError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
-}
-
-impl From<TooPreciseError> for ParseError {
- fn from(e: TooPreciseError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
-}
-
-impl From<MissingDigitsError> for ParseError {
- fn from(e: MissingDigitsError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
-}
-
-impl From<InputTooLargeError> for ParseError {
- fn from(e: InputTooLargeError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
-}
-
-impl From<InvalidCharacterError> for ParseError {
- fn from(e: InvalidCharacterError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
-}
-
-impl From<BadPositionError> for ParseError {
- fn from(e: BadPositionError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
-}
-
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
@@ -113,36 +81,6 @@ pub(crate) enum ParseAmountErrorInner {
UnprefixedHex(UnprefixedHexError),
}
-impl From<TooPreciseError> for ParseAmountError {
- fn from(value: TooPreciseError) -> Self { Self(ParseAmountErrorInner::TooPrecise(value)) }
-}
-
-impl From<MissingDigitsError> for ParseAmountError {
- fn from(value: MissingDigitsError) -> Self { Self(ParseAmountErrorInner::MissingDigits(value)) }
-}
-
-impl From<InputTooLargeError> for ParseAmountError {
- fn from(value: InputTooLargeError) -> Self { Self(ParseAmountErrorInner::InputTooLarge(value)) }
-}
-
-impl From<InvalidCharacterError> for ParseAmountError {
- fn from(value: InvalidCharacterError) -> Self {
- Self(ParseAmountErrorInner::InvalidCharacter(value))
- }
-}
-
-impl From<BadPositionError> for ParseAmountError {
- fn from(value: BadPositionError) -> Self { Self(ParseAmountErrorInner::BadPosition(value)) }
-}
-
-impl From<PrefixedHexError> for ParseAmountError {
- fn from(value: PrefixedHexError) -> Self { Self(ParseAmountErrorInner::PrefixedHex(value)) }
-}
-
-impl From<UnprefixedHexError> for ParseAmountError {
- fn from(value: UnprefixedHexError) -> Self { Self(ParseAmountErrorInner::UnprefixedHex(value)) }
-}
-
impl From<Infallible> for ParseAmountError {
fn from(never: Infallible) -> Self { match never {} }
}
@@ -246,10 +184,6 @@ impl fmt::Display for OutOfRangeError {
#[cfg(feature = "std")]
impl std::error::Error for OutOfRangeError {}
-impl From<OutOfRangeError> for ParseAmountError {
- fn from(value: OutOfRangeError) -> Self { Self(ParseAmountErrorInner::OutOfRange(value)) }
-}
-
/// Error returned when the input string has higher precision than satoshis.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TooPreciseError {
diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs
index 953196f5..71b3fa51 100644
--- a/units/src/amount/mod.rs
+++ b/units/src/amount/mod.rs
@@ -356,7 +356,10 @@ impl InnerParseError {
fn convert(self, is_signed: bool) -> ParseAmountError {
match self {
Self::Overflow { is_negative } =>
- OutOfRangeError { is_signed, is_greater_than_max: !is_negative }.into(),
+ ParseAmountError(ParseAmountErrorInner::OutOfRange(OutOfRangeError {
+ is_signed,
+ is_greater_than_max: !is_negative,
+ })),
Self::TooPrecise(e) => ParseAmountError(ParseAmountErrorInner::TooPrecise(e)),
Self::MissingDigits(e) => ParseAmountError(ParseAmountErrorInner::MissingDigits(e)),
Self::InputTooLarge(len) =>
@@ -377,7 +380,7 @@ fn split_amount_and_denomination(s: &str) -> Result<(&str, Denomination), ParseE
.ok_or(ParseError(ParseErrorInner::MissingDenomination(MissingDenominationError)))?;
(i, i)
};
- Ok((&s[..i], s[j..].parse()?))
+ Ok((&s[..i], s[j..].parse().map_err(|e| ParseError(ParseErrorInner::Denomination(e)))?))
}
/// Options given by `fmt::Formatter`
diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs
index 7f39e5b8..59c62945 100644
--- a/units/src/amount/signed.rs
+++ b/units/src/amount/signed.rs
@@ -10,7 +10,7 @@ use core::{default, fmt};
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
-use super::error::ParseErrorInner;
+use super::error::{ParseAmountErrorInner, ParseErrorInner};
use super::{
parse_signed_to_satoshi, split_amount_and_denomination, Amount, Denomination, Display,
DisplayStyle, OutOfRangeError, ParseAmountError, ParseError,
@@ -131,9 +131,14 @@ impl SignedAmount {
#[allow(clippy::missing_panics_doc)]
fn from_sat_u64(satoshi: u64) -> Result<Self, ParseAmountError> {
// u64 -> i64 only fails if value is greater than i64::MAX, which is also > Self::MAX_MONEY.
- let amount = i64::try_from(satoshi)
- .map_err(|_| OutOfRangeError { is_signed: true, is_greater_than_max: true })?;
- Ok(Self::from_sat(amount)?)
+ let amount = i64::try_from(satoshi).map_err(|_| {
+ ParseAmountError(ParseAmountErrorInner::OutOfRange(OutOfRangeError {
+ is_signed: true,
+ is_greater_than_max: true,
+ }))
+ })?;
+ Ok(Self::from_sat(amount)
+ .map_err(|e| ParseAmountError(ParseAmountErrorInner::OutOfRange(e)))?)
}
/// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
@@ -204,12 +209,12 @@ impl SignedAmount {
/// ```
/// # use bitcoin_units::{amount, SignedAmount};
/// let amount = SignedAmount::from_str_with_denomination("0.1 BTC")?;
- /// assert_eq!(amount, SignedAmount::from_sat(10_000_000)?);
+ /// assert_eq!(amount, SignedAmount::from_sat_i32(10_000_000));
/// # Ok::<_, amount::ParseError>(())
/// ```
pub fn from_str_with_denomination(s: &str) -> Result<Self, ParseError> {
let (amt, denom) = split_amount_and_denomination(s)?;
- Self::from_str_in(amt, denom).map_err(Into::into)
+ Self::from_str_in(amt, denom).map_err(|e| ParseError(ParseErrorInner::Amount(e)))
}
/// Expresses this [`SignedAmount`] as a floating-point value in the given [`Denomination`].
@@ -240,7 +245,8 @@ impl SignedAmount {
/// include the `0x` prefix.
#[inline]
pub fn from_sat_hex(s: &str) -> Result<Self, ParseAmountError> {
- let amount = parse_int::hex_u64_prefixed(s)?;
+ let amount = parse_int::hex_u64_prefixed(s)
+ .map_err(|e| ParseAmountError(ParseAmountErrorInner::PrefixedHex(e)))?;
Self::from_sat_u64(amount)
}
@@ -254,7 +260,8 @@ impl SignedAmount {
/// includes the `0x` prefix.
#[inline]
pub fn from_sat_unprefixed_hex(s: &str) -> Result<Self, ParseAmountError> {
- let amount = parse_int::hex_u64_unprefixed(s)?;
+ let amount = parse_int::hex_u64_unprefixed(s)
+ .map_err(|e| ParseAmountError(ParseAmountErrorInner::UnprefixedHex(e)))?;
Self::from_sat_u64(amount)
}
diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs
index 64022b90..ef1d0ce4 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -16,6 +16,15 @@ use crate::result::{MathOp, NumOpError, NumOpResult};
use crate::FeeRate;
use crate::Weight;
+fn amt_err(e: ParseAmountErrorInner) -> ParseAmountError { ParseAmountError(e) }
+
+fn parse_err(e: ParseAmountErrorInner) -> ParseError {
+ ParseError(ParseErrorInner::Amount(ParseAmountError(e)))
+}
+fn denom_err(e: ParseDenominationError) -> ParseError {
+ ParseError(ParseErrorInner::Denomination(e))
+}
+
#[track_caller]
fn sat(sat: u64) -> Amount { Amount::from_sat(sat).unwrap() }
@@ -362,22 +371,31 @@ fn floating_point() {
assert_eq!(f(0.000_123_4, D::Bitcoin), Ok(sat(12_340)));
assert_eq!(sf(-0.000_123_45, D::Bitcoin), Ok(ssat(-12_345)));
- assert_eq!(f(11.22, D::Satoshi), Err(TooPreciseError { position: 3 }.into()));
- assert_eq!(f(42.123_456_781, D::Bitcoin), Err(TooPreciseError { position: 11 }.into()));
- assert_eq!(sf(-184_467_440_738.0, D::Bitcoin), Err(OutOfRangeError::too_small().into()));
+ assert_eq!(
+ f(11.22, D::Satoshi),
+ Err(amt_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 3 })))
+ );
+ assert_eq!(
+ f(42.123_456_781, D::Bitcoin),
+ Err(amt_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 11 })))
+ );
+ assert_eq!(
+ sf(-184_467_440_738.0, D::Bitcoin),
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small())))
+ );
assert_eq!(
f(18_446_744_073_709_551_617.0, D::Satoshi),
- Err(OutOfRangeError::too_big(false).into())
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(false))))
);
assert_eq!(
f(Amount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
- Err(OutOfRangeError::too_big(false).into())
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(false))))
);
assert_eq!(
sf(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
- Err(OutOfRangeError::too_big(true).into())
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true))))
);
let btc = move |f| SignedAmount::from_btc(f).unwrap();
@@ -402,38 +420,69 @@ fn parsing() {
assert_eq!(
p("x", den_btc),
- Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 0 }))
+ Err(amt_err(ParseAmountErrorInner::InvalidCharacter(InvalidCharacterError {
+ invalid_char: 'x',
+ position: 0
+ })))
);
assert_eq!(
p("-", den_btc),
- Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
+ Err(amt_err(ParseAmountErrorInner::MissingDigits(MissingDigitsError {
+ kind: MissingDigitsKind::OnlyMinusSign
+ })))
);
assert_eq!(
sp("-", den_btc),
- Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
+ Err(amt_err(ParseAmountErrorInner::MissingDigits(MissingDigitsError {
+ kind: MissingDigitsKind::OnlyMinusSign
+ })))
);
assert_eq!(
p("-1.0x", den_btc),
- Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 4 }))
+ Err(amt_err(ParseAmountErrorInner::InvalidCharacter(InvalidCharacterError {
+ invalid_char: 'x',
+ position: 4
+ })))
);
assert_eq!(
p("0.0 ", den_btc),
- Err(E::from(InvalidCharacterError { invalid_char: ' ', position: 3 }))
+ Err(amt_err(ParseAmountErrorInner::InvalidCharacter(InvalidCharacterError {
+ invalid_char: ' ',
+ position: 3
+ })))
);
assert_eq!(
p("0.000.000", den_btc),
- Err(E::from(InvalidCharacterError { invalid_char: '.', position: 5 }))
+ Err(amt_err(ParseAmountErrorInner::InvalidCharacter(InvalidCharacterError {
+ invalid_char: '.',
+ position: 5
+ })))
);
#[cfg(feature = "alloc")]
let more_than_max = format!("{}", Amount::MAX.to_sat() + 1);
#[cfg(feature = "alloc")]
- assert_eq!(p(&more_than_max, den_btc), Err(OutOfRangeError::too_big(false).into()));
- assert_eq!(p("0.000000042", den_btc), Err(TooPreciseError { position: 10 }.into()));
+ assert_eq!(
+ p(&more_than_max, den_btc),
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(false))))
+ );
+ assert_eq!(
+ p("0.000000042", den_btc),
+ Err(amt_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 10 })))
+ );
assert_eq!(p("1.0000000", den_sat), Ok(sat(1)));
- assert_eq!(p("1.1", den_sat), Err(TooPreciseError { position: 2 }.into()));
- assert_eq!(p("1000.1", den_sat), Err(TooPreciseError { position: 5 }.into()));
+ assert_eq!(
+ p("1.1", den_sat),
+ Err(amt_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 2 })))
+ );
+ assert_eq!(
+ p("1000.1", den_sat),
+ Err(amt_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 5 })))
+ );
assert_eq!(p("1001.0000000", den_sat), Ok(sat(1001)));
- assert_eq!(p("1000.0000001", den_sat), Err(TooPreciseError { position: 11 }.into()));
+ assert_eq!(
+ p("1000.0000001", den_sat),
+ Err(amt_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 11 })))
+ );
assert_eq!(p("1", den_btc), Ok(sat(1_000_000_00)));
assert_eq!(sp("-.5", den_btc), Ok(ssat(-500_000_00)));
@@ -449,7 +498,7 @@ fn parsing() {
// exactly 50 chars.
assert_eq!(
p("100000000000000.0000000000000000000000000000000000", Denomination::Bitcoin),
- Err(OutOfRangeError::too_big(false).into())
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(false))))
);
// more than 50 chars.
assert_eq!(
@@ -659,28 +708,45 @@ fn unsigned_signed_conversion() {
#[test]
#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin.
#[allow(clippy::items_after_statements)] // Define functions where we use them.
+#[allow(clippy::too_many_lines)]
fn from_str() {
- use super::{ParseAmountError as E, ParseDenominationError};
+ use super::ParseDenominationError;
assert_eq!(
"x BTC".parse::<Amount>(),
- Err(InvalidCharacterError { invalid_char: 'x', position: 0 }.into())
+ Err(ParseError(ParseErrorInner::Amount(ParseAmountError(
+ ParseAmountErrorInner::InvalidCharacter(InvalidCharacterError {
+ invalid_char: 'x',
+ position: 0
+ })
+ ))))
);
assert_eq!(
"xBTC".parse::<Amount>(),
- Err(ParseDenominationError::Unknown(UnknownDenominationError("xBTC".into())).into()),
+ Err(ParseError(ParseErrorInner::Denomination(ParseDenominationError::Unknown(
+ UnknownDenominationError("xBTC".into())
+ )))),
);
assert_eq!(
"5 BTC BTC".parse::<Amount>(),
- Err(ParseDenominationError::Unknown(UnknownDenominationError("BTC BTC".into())).into()),
+ Err(ParseError(ParseErrorInner::Denomination(ParseDenominationError::Unknown(
+ UnknownDenominationError("BTC BTC".into())
+ )))),
);
assert_eq!(
"5BTC BTC".parse::<Amount>(),
- Err(E::from(InvalidCharacterError { invalid_char: 'B', position: 1 }).into())
+ Err(ParseError(ParseErrorInner::Amount(ParseAmountError(
+ ParseAmountErrorInner::InvalidCharacter(InvalidCharacterError {
+ invalid_char: 'B',
+ position: 1
+ })
+ ))))
);
assert_eq!(
"5 5 BTC".parse::<Amount>(),
- Err(ParseDenominationError::Unknown(UnknownDenominationError("5 BTC".into())).into()),
+ Err(ParseError(ParseErrorInner::Denomination(ParseDenominationError::Unknown(
+ UnknownDenominationError("5 BTC".into())
+ )))),
);
#[track_caller]
@@ -690,10 +756,9 @@ fn from_str() {
}
#[track_caller]
- fn case(s: &str, expected: Result<Amount, impl Into<ParseError>>) {
- let expected = expected.map_err(Into::into);
- assert_eq!(s.parse::<Amount>(), expected);
- assert_eq!(s.replace(' ', "").parse::<Amount>(), expected);
+ fn case(s: &str, expected: &Result<Amount, ParseError>) {
+ assert_eq!(s.parse::<Amount>(), *expected);
+ assert_eq!(s.replace(' ', "").parse::<Amount>(), *expected);
}
#[track_caller]
@@ -703,29 +768,82 @@ fn from_str() {
}
#[track_caller]
- fn scase(s: &str, expected: Result<SignedAmount, impl Into<ParseError>>) {
- let expected = expected.map_err(Into::into);
- assert_eq!(s.parse::<SignedAmount>(), expected);
- assert_eq!(s.replace(' ', "").parse::<SignedAmount>(), expected);
+ fn scase(s: &str, expected: &Result<SignedAmount, ParseError>) {
+ assert_eq!(s.parse::<SignedAmount>(), *expected);
+ assert_eq!(s.replace(' ', "").parse::<SignedAmount>(), *expected);
}
- case("5 BCH", Err(ParseDenominationError::Unknown(UnknownDenominationError("BCH".into()))));
-
- case("-1 BTC", Err(OutOfRangeError::negative()));
- case("-0.0 BTC", Err(OutOfRangeError::negative()));
- case("0.123456789 BTC", Err(TooPreciseError { position: 10 }));
- scase("-0.1 satoshi", Err(TooPreciseError { position: 3 }));
- case("0.123456 mBTC", Err(TooPreciseError { position: 7 }));
- scase("-1.001 bits", Err(TooPreciseError { position: 5 }));
- scase("-21000001 BTC", Err(OutOfRangeError::too_small()));
- scase("21000001 BTC", Err(OutOfRangeError::too_big(true)));
- scase("-2100000000000001 SAT", Err(OutOfRangeError::too_small()));
- scase("2100000000000001 SAT", Err(OutOfRangeError::too_big(true)));
- case("21000001 BTC", Err(OutOfRangeError::too_big(false)));
- case("18446744073709551616 sat", Err(OutOfRangeError::too_big(false)));
- case("_1000 sat", Err(BadPositionError { char: '_', position: 0 }));
- case("10__00 sat", Err(BadPositionError { char: '_', position: 3 }));
- scase("-_10_00 sat", Err(BadPositionError { char: '_', position: 1 }));
+ case(
+ "5 BCH",
+ &Err(denom_err(ParseDenominationError::Unknown(UnknownDenominationError("BCH".into())))),
+ );
+
+ case("-1 BTC", &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::negative()))));
+ case(
+ "-0.0 BTC",
+ &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::negative()))),
+ );
+ case(
+ "0.123456789 BTC",
+ &Err(parse_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 10 }))),
+ );
+ scase(
+ "-0.1 satoshi",
+ &Err(parse_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 3 }))),
+ );
+ case(
+ "0.123456 mBTC",
+ &Err(parse_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 7 }))),
+ );
+ scase(
+ "-1.001 bits",
+ &Err(parse_err(ParseAmountErrorInner::TooPrecise(TooPreciseError { position: 5 }))),
+ );
+ scase(
+ "-21000001 BTC",
+ &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small()))),
+ );
+ scase(
+ "21000001 BTC",
+ &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true)))),
+ );
+ scase(
+ "-2100000000000001 SAT",
+ &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small()))),
+ );
+ scase(
+ "2100000000000001 SAT",
+ &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true)))),
+ );
+ case(
+ "21000001 BTC",
+ &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(false)))),
+ );
+ case(
+ "18446744073709551616 sat",
+ &Err(parse_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(false)))),
+ );
+ case(
+ "_1000 sat",
+ &Err(parse_err(ParseAmountErrorInner::BadPosition(BadPositionError {
+ char: '_',
+ position: 0,
+ }))),
+ );
+ case(
+ "10__00 sat",
+ &Err(parse_err(ParseAmountErrorInner::BadPosition(BadPositionError {
+ char: '_',
+ position: 3,
+ }))),
+ );
+ scase(
+ "-_10_00 sat",
+ &Err(parse_err(ParseAmountErrorInner::BadPosition(BadPositionError {
+ char: '_',
+ position: 1,
+ }))),
+ );
ok_case(".5 bits", sat(50));
ok_scase("-.5 bits", ssat(-50));
@@ -809,12 +927,12 @@ fn to_from_string_in() {
assert_eq!(
sa_str(&SignedAmount::MAX.to_string_in(D::Satoshi), D::MicroBitcoin),
- Err(OutOfRangeError::too_big(true).into())
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true))))
);
// Test an overflow bug in `abs()`
assert_eq!(
sa_str(&SignedAmount::MIN.to_string_in(D::Satoshi), D::MicroBitcoin),
- Err(OutOfRangeError::too_small().into())
+ Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small())))
);
}
@@ -834,11 +952,15 @@ fn to_string_with_denomination_from_str_roundtrip() {
assert_eq!(
"42 satoshi BTC".parse::<Amount>(),
- Err(ParseDenominationError::Unknown(UnknownDenominationError("satoshi BTC".into())).into(),),
+ Err(ParseError(ParseErrorInner::Denomination(ParseDenominationError::Unknown(
+ UnknownDenominationError("satoshi BTC".into())
+ ))))
);
assert_eq!(
"-42 satoshi BTC".parse::<SignedAmount>(),
- Err(ParseDenominationError::Unknown(UnknownDenominationError("satoshi BTC".into())).into(),),
+ Err(ParseError(ParseErrorInner::Denomination(ParseDenominationError::Unknown(
+ UnknownDenominationError("satoshi BTC".into())
+ )))),
);
}
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index e82eaf8e..c052f0a9 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -201,12 +201,12 @@ impl Amount {
/// ```
/// # use bitcoin_units::{amount, Amount};
/// let amount = Amount::from_str_with_denomination("0.1 BTC")?;
- /// assert_eq!(amount, Amount::from_sat(10_000_000)?);
+ /// assert_eq!(amount, Amount::from_sat_u32(10_000_000));
/// # Ok::<_, amount::ParseError>(())
/// ```
pub fn from_str_with_denomination(s: &str) -> Result<Self, ParseError> {
let (amt, denom) = split_amount_and_denomination(s)?;
- Self::from_str_in(amt, denom).map_err(Into::into)
+ Self::from_str_in(amt, denom).map_err(|e| ParseError(ParseErrorInner::Amount(e)))
}
/// Expresses this [`Amount`] as a floating-point value in the given [`Denomination`].
@@ -219,7 +219,7 @@ impl Amount {
/// # use bitcoin_units::amount::{self, Amount, Denomination};
/// let amount = Amount::from_sat(100_000)?;
/// assert_eq!(amount.to_float_in(Denomination::Bitcoin), 0.001);
- /// # Ok::<_, amount::ParseError>(())
+ /// # Ok::<_, amount::OutOfRangeError>(())
/// ```
#[cfg(feature = "alloc")]
#[allow(clippy::missing_panics_doc)]
@@ -237,7 +237,7 @@ impl Amount {
/// # use bitcoin_units::amount::{self, Amount, Denomination};
/// let amount = Amount::from_sat(100_000)?;
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin));
- /// # Ok::<_, amount::ParseError>(())
+ /// # Ok::<_, amount::OutOfRangeError>(())
/// ```
#[cfg(feature = "alloc")]
pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
@@ -252,7 +252,9 @@ impl Amount {
#[cfg(feature = "alloc")]
pub fn from_float_in(value: f64, denom: Denomination) -> Result<Self, ParseAmountError> {
if value < 0.0 {
- return Err(OutOfRangeError::negative().into());
+ return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
+ OutOfRangeError::negative(),
+ )));
}
// This is inefficient, but the safest way to deal with this. The parsing logic is safe.
// Any performance-critical application should not be dealing with floats.
@@ -267,8 +269,10 @@ impl Amount {
/// include the `0x` prefix.
#[inline]
pub fn from_sat_hex(s: &str) -> Result<Self, ParseAmountError> {
- let amount = parse_int::hex_u64_prefixed(s)?;
- Ok(Self::from_sat(amount)?)
+ let amount = parse_int::hex_u64_prefixed(s)
+ .map_err(|e| ParseAmountError(ParseAmountErrorInner::PrefixedHex(e)))?;
+ Ok(Self::from_sat(amount)
+ .map_err(|e| ParseAmountError(ParseAmountErrorInner::OutOfRange(e)))?)
}
/// Constructs a new `Amount` from an unprefixed hex string.
@@ -279,8 +283,10 @@ impl Amount {
/// includes the `0x` prefix.
#[inline]
pub fn from_sat_unprefixed_hex(s: &str) -> Result<Self, ParseAmountError> {
- let amount = parse_int::hex_u64_unprefixed(s)?;
- Ok(Self::from_sat(amount)?)
+ let amount = parse_int::hex_u64_unprefixed(s)
+ .map_err(|e| ParseAmountError(ParseAmountErrorInner::UnprefixedHex(e)))?;
+ Ok(Self::from_sat(amount)
+ .map_err(|e| ParseAmountError(ParseAmountErrorInner::OutOfRange(e)))?)
}
/// Constructs a new object that implements [`fmt::Display`] in the given [`Denomination`].
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.