forked from rust-bitcoin/rust-bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
We would like to return an error when doing match ops on amount types. We cannot however use the stdlib `Result` or `Option` because we want to implement ops on the result type. Add an `AmountOpResult` type. Return this type from all math operations on `Amount` and `SignedAmount`. Implement `core::iter::Sum` for the new type to allow summing iterators of amounts - somewhat ugly to use, see tests for example usage.
- Loading branch information
Showing
10 changed files
with
516 additions
and
201 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
// SPADIX-License-Identifier: CC0-1.0 | ||
|
||
//! Provides a monodic result type that is used to return the result of | ||
//! doing mathematical operations (`core::ops`) on amount types. | ||
use core::{fmt, ops}; | ||
|
||
use super::{Amount, SignedAmount}; | ||
|
||
/// Result of an operation on [`Amount`] or [`SignedAmount`]. | ||
/// | ||
/// The type parameter `T` should be normally `Amout` or `SignedAmount`. | ||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub enum AmountOpResult<T> { | ||
/// Result of a successful mathematical operation. | ||
Valid(T), | ||
/// Result of an unsuccessful mathematical operation. | ||
Error(AmountOpError), | ||
} | ||
|
||
impl<T> AmountOpResult<T> { | ||
/// Returns the contained valid amount, consuming `self`. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if the result is an `Error`. | ||
pub fn unwrap_valid(self) -> T { | ||
use AmountOpResult as R; | ||
|
||
match self { | ||
R::Valid(amount) => amount, | ||
R::Error(_) => panic!("tried to unwrap an invalid result"), | ||
} | ||
} | ||
|
||
/// Returns the contained error, consuming `self`. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if the result is a valid amount. | ||
pub fn unwrap_error(self) -> AmountOpError { | ||
use AmountOpResult as R; | ||
|
||
match self { | ||
R::Error(e) => e, | ||
R::Valid(_) => panic!("tried to unwrap a valid result"), | ||
} | ||
} | ||
|
||
/// Converts this `AmountOpResult` to a `Result<T, AmountOpError>`. | ||
pub fn into_result(self) -> Result<T, AmountOpError> { | ||
use AmountOpResult as R; | ||
|
||
match self { | ||
R::Valid(amount) => Ok(amount), | ||
R::Error(e) => Err(e), | ||
} | ||
} | ||
|
||
/// Returns `true` if this result is a valid amount. | ||
pub fn is_valid(&self) -> bool { | ||
use AmountOpResult as R; | ||
|
||
match self { | ||
R::Valid(_) => true, | ||
R::Error(_) => false, | ||
} | ||
} | ||
|
||
/// Returns `true` if this result is an invalid amount. | ||
pub fn is_error(&self) -> bool { !self.is_valid() } | ||
} | ||
|
||
impl From<Amount> for AmountOpResult<Amount> { | ||
fn from(a: Amount) -> Self { Self::Valid(a) } | ||
} | ||
|
||
impl From<SignedAmount> for AmountOpResult<SignedAmount> { | ||
fn from(a: SignedAmount) -> Self { Self::Valid(a) } | ||
} | ||
|
||
impl<T> ops::Add for AmountOpResult<T> | ||
where | ||
T: ops::Add<Output = AmountOpResult<T>>, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn add(self, rhs: Self) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
impl<T> ops::Add<AmountOpResult<T>> for &AmountOpResult<T> | ||
where | ||
T: ops::Add<Output = AmountOpResult<T>> + Copy, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn add(self, rhs: AmountOpResult<T>) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => *lhs + rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
impl<T> ops::Add<&AmountOpResult<T>> for AmountOpResult<T> | ||
where | ||
T: ops::Add<Output = AmountOpResult<T>> + Copy, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn add(self, rhs: &AmountOpResult<T>) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs + *rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
impl<T> ops::Add for &AmountOpResult<T> | ||
where | ||
T: ops::Add<Output = AmountOpResult<T>> + Copy, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn add(self, rhs: &AmountOpResult<T>) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => *lhs + *rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
|
||
impl<T> ops::Sub for AmountOpResult<T> | ||
where | ||
T: ops::Sub<Output = AmountOpResult<T>>, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn sub(self, rhs: Self) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs - rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
impl<T> ops::Sub<AmountOpResult<T>> for &AmountOpResult<T> | ||
where | ||
T: ops::Sub<Output = AmountOpResult<T>> + Copy, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn sub(self, rhs: AmountOpResult<T>) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => *lhs - rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
impl<T> ops::Sub<&AmountOpResult<T>> for AmountOpResult<T> | ||
where | ||
T: ops::Sub<Output = AmountOpResult<T>> + Copy, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn sub(self, rhs: &AmountOpResult<T>) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs - *rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
impl<T> ops::Sub for &AmountOpResult<T> | ||
where | ||
T: ops::Sub<Output = AmountOpResult<T>> + Copy, | ||
{ | ||
type Output = AmountOpResult<T>; | ||
|
||
fn sub(self, rhs: Self) -> Self::Output { | ||
use AmountOpResult as R; | ||
|
||
match (self, rhs) { | ||
(R::Valid(lhs), R::Valid(rhs)) => *lhs - *rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
|
||
impl core::iter::Sum<AmountOpResult<Amount>> for AmountOpResult<Amount> { | ||
fn sum<I>(iter: I) -> Self | ||
where | ||
I: Iterator<Item = AmountOpResult<Amount>>, | ||
{ | ||
use AmountOpResult as R; | ||
|
||
iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
}) | ||
} | ||
} | ||
impl<'a> core::iter::Sum<AmountOpResult<&'a Amount>> for AmountOpResult<Amount> { | ||
fn sum<I>(iter: I) -> Self | ||
where | ||
I: Iterator<Item = AmountOpResult<&'a Amount>>, | ||
{ | ||
use AmountOpResult as R; | ||
|
||
iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
}) | ||
} | ||
} | ||
impl<'a> core::iter::Sum<&'a AmountOpResult<Amount>> for AmountOpResult<Amount> { | ||
fn sum<I>(iter: I) -> Self | ||
where | ||
I: Iterator<Item = &'a AmountOpResult<Amount>>, | ||
{ | ||
use AmountOpResult as R; | ||
|
||
iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
}) | ||
} | ||
} | ||
|
||
impl core::iter::Sum<SignedAmount> for AmountOpResult<SignedAmount> { | ||
fn sum<I>(iter: I) -> AmountOpResult<SignedAmount> | ||
where | ||
I: Iterator<Item = SignedAmount>, | ||
{ | ||
use AmountOpResult as R; | ||
|
||
iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match acc { | ||
R::Valid(lhs) => lhs + amount, | ||
R::Error(e) => R::Error(e), | ||
}) | ||
} | ||
} | ||
impl<'a> core::iter::Sum<AmountOpResult<&'a SignedAmount>> for AmountOpResult<SignedAmount> { | ||
fn sum<I>(iter: I) -> Self | ||
where | ||
I: Iterator<Item = AmountOpResult<&'a SignedAmount>>, | ||
{ | ||
use AmountOpResult as R; | ||
|
||
iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
}) | ||
} | ||
} | ||
impl<'a> core::iter::Sum<&'a AmountOpResult<SignedAmount>> for AmountOpResult<SignedAmount> { | ||
fn sum<I>(iter: I) -> Self | ||
where | ||
I: Iterator<Item = &'a AmountOpResult<SignedAmount>>, | ||
{ | ||
use AmountOpResult as R; | ||
|
||
iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) { | ||
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, | ||
(_, _) => R::Error(AmountOpError {}), | ||
}) | ||
} | ||
} | ||
|
||
pub(in crate::amount) trait OptionExt<T> { | ||
fn unwrap_or_amount_op_error(self) -> AmountOpResult<T>; | ||
} | ||
|
||
impl OptionExt<Amount> for Option<Amount> { | ||
fn unwrap_or_amount_op_error(self) -> AmountOpResult<Amount> { | ||
use AmountOpResult as R; | ||
|
||
match self { | ||
Some(amount) => R::Valid(amount), | ||
None => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
|
||
impl OptionExt<SignedAmount> for Option<SignedAmount> { | ||
fn unwrap_or_amount_op_error(self) -> AmountOpResult<SignedAmount> { | ||
use AmountOpResult as R; | ||
|
||
match self { | ||
Some(amount) => R::Valid(amount), | ||
None => R::Error(AmountOpError {}), | ||
} | ||
} | ||
} | ||
|
||
/// An error occurred while doing a mathematical operation. | ||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
#[non_exhaustive] | ||
pub struct AmountOpError; | ||
|
||
impl fmt::Display for AmountOpError { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "a math operation on amounts gave an invalid result") | ||
} | ||
} | ||
|
||
#[cfg(feature = "std")] | ||
impl std::error::Error for AmountOpError {} |
Oops, something went wrong.