// SPDX-License-Identifier: CC0-1.0 //! Provides a monodic numeric result type that is used to return the result of //! doing mathematical operations (`core::ops`) on amount types. use core::{fmt, ops}; use NumOpResult as R; use super::{Amount, SignedAmount}; /// Result of an operation on [`Amount`] or [`SignedAmount`]. /// /// The type parameter `T` should be normally `Amout` or `SignedAmount`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[must_use] pub enum NumOpResult { /// Result of a successful mathematical operation. Valid(T), /// Result of an unsuccessful mathematical operation. Error(NumOpError), } impl NumOpResult { /// Returns the contained valid amount, consuming `self`. /// /// # Panics /// /// Panics with `msg` if the numeric result is an `Error`. #[inline] #[track_caller] pub fn expect(self, msg: &str) -> T { match self { R::Valid(amount) => amount, R::Error(_) => panic!("{}", msg), } } /// Returns the contained valid amount, consuming `self`. /// /// # Panics /// /// Panics if the numeric result is an `Error`. #[inline] #[track_caller] pub fn unwrap(self) -> T { match self { R::Valid(amount) => amount, R::Error(e) => panic!("tried to unwrap an invalid numeric result: {:?}", e), } } /// Returns the contained error, consuming `self`. /// /// # Panics /// /// Panics if the numeric result is a valid amount. #[inline] #[track_caller] pub fn unwrap_err(self) -> NumOpError { match self { R::Error(e) => e, R::Valid(a) => panic!("tried to unwrap a valid numeric result: {:?}", a), } } /// Converts this `NumOpResult` to an `Option`. #[inline] pub fn ok(self) -> Option { match self { R::Valid(amount) => Some(amount), R::Error(_) => None, } } /// Converts this `NumOpResult` to a `Result`. #[inline] #[allow(clippy::missing_errors_doc)] pub fn into_result(self) -> Result { match self { R::Valid(amount) => Ok(amount), R::Error(e) => Err(e), } } /// Calls `op` if the numeric result is `Valid`, otherwise returns the `Error` value of `self`. #[inline] pub fn and_then(self, op: F) -> NumOpResult where F: FnOnce(T) -> NumOpResult, { match self { R::Valid(amount) => op(amount), R::Error(e) => R::Error(e), } } /// Returns `true` if the numeric result is a valid amount. #[inline] pub fn is_valid(&self) -> bool { match self { R::Valid(_) => true, R::Error(_) => false, } } /// Returns `true` if the numeric result is an invalid amount. #[inline] pub fn is_error(&self) -> bool { !self.is_valid() } } impl From for NumOpResult { fn from(a: Amount) -> Self { Self::Valid(a) } } impl From<&Amount> for NumOpResult { fn from(a: &Amount) -> Self { Self::Valid(*a) } } impl From for NumOpResult { fn from(a: SignedAmount) -> Self { Self::Valid(a) } } impl From<&SignedAmount> for NumOpResult { fn from(a: &SignedAmount) -> Self { Self::Valid(*a) } } crate::internal_macros::impl_op_for_references! { impl ops::Add for Amount { type Output = NumOpResult; fn add(self, rhs: Amount) -> Self::Output { self.checked_add(rhs).valid_or_error() } } impl ops::Add> for Amount { type Output = NumOpResult; fn add(self, rhs: NumOpResult) -> Self::Output { rhs.and_then(|a| a + self) } } impl ops::Sub for Amount { type Output = NumOpResult; fn sub(self, rhs: Amount) -> Self::Output { self.checked_sub(rhs).valid_or_error() } } impl ops::Sub> for Amount { type Output = NumOpResult; fn sub(self, rhs: NumOpResult) -> Self::Output { match rhs { R::Valid(amount) => self - amount, R::Error(_) => rhs, } } } impl ops::Mul for Amount { type Output = NumOpResult; fn mul(self, rhs: u64) -> Self::Output { self.checked_mul(rhs).valid_or_error() } } impl ops::Mul for NumOpResult { type Output = NumOpResult; fn mul(self, rhs: u64) -> Self::Output { self.and_then(|lhs| lhs * rhs) } } impl ops::Div for Amount { type Output = NumOpResult; fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).valid_or_error() } } impl ops::Div for NumOpResult { type Output = NumOpResult; fn div(self, rhs: u64) -> Self::Output { self.and_then(|lhs| lhs / rhs) } } impl ops::Rem for Amount { type Output = NumOpResult; fn rem(self, modulus: u64) -> Self::Output { self.checked_rem(modulus).valid_or_error() } } impl ops::Rem for NumOpResult { type Output = NumOpResult; fn rem(self, modulus: u64) -> Self::Output { self.and_then(|lhs| lhs % modulus) } } impl ops::Add for SignedAmount { type Output = NumOpResult; fn add(self, rhs: SignedAmount) -> Self::Output { self.checked_add(rhs).valid_or_error() } } impl ops::Add> for SignedAmount { type Output = NumOpResult; fn add(self, rhs: NumOpResult) -> Self::Output { rhs.and_then(|a| a + self) } } impl ops::Sub for SignedAmount { type Output = NumOpResult; fn sub(self, rhs: SignedAmount) -> Self::Output { self.checked_sub(rhs).valid_or_error() } } impl ops::Sub> for SignedAmount { type Output = NumOpResult; fn sub(self, rhs: NumOpResult) -> Self::Output { match rhs { R::Valid(amount) => self - amount, R::Error(_) => rhs, } } } impl ops::Mul for SignedAmount { type Output = NumOpResult; fn mul(self, rhs: i64) -> Self::Output { self.checked_mul(rhs).valid_or_error() } } impl ops::Mul for NumOpResult { type Output = NumOpResult; fn mul(self, rhs: i64) -> Self::Output { self.and_then(|lhs| lhs * rhs) } } impl ops::Div for SignedAmount { type Output = NumOpResult; fn div(self, rhs: i64) -> Self::Output { self.checked_div(rhs).valid_or_error() } } impl ops::Div for NumOpResult { type Output = NumOpResult; fn div(self, rhs: i64) -> Self::Output { self.and_then(|lhs| lhs / rhs) } } impl ops::Rem for SignedAmount { type Output = NumOpResult; fn rem(self, modulus: i64) -> Self::Output { self.checked_rem(modulus).valid_or_error() } } impl ops::Rem for NumOpResult { type Output = NumOpResult; fn rem(self, modulus: i64) -> Self::Output { self.and_then(|lhs| lhs % modulus) } } impl ops::Add> for NumOpResult where (T: Copy + ops::Add>) { type Output = NumOpResult; fn add(self, rhs: Self) -> Self::Output { match (self, rhs) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (_, _) => R::Error(NumOpError {}), } } } impl ops::Add for NumOpResult where (T: Copy + ops::Add, Output = NumOpResult>) { type Output = NumOpResult; fn add(self, rhs: T) -> Self::Output { rhs + self } } impl ops::Sub> for NumOpResult where (T: Copy + ops::Sub>) { type Output = NumOpResult; fn sub(self, rhs: Self) -> Self::Output { match (self, rhs) { (R::Valid(lhs), R::Valid(rhs)) => lhs - rhs, (_, _) => R::Error(NumOpError {}), } } } impl ops::Sub for NumOpResult where (T: Copy + ops::Sub>) { type Output = NumOpResult; fn sub(self, rhs: T) -> Self::Output { match self { R::Valid(amount) => amount - rhs, R::Error(_) => self, } } } } impl ops::Neg for SignedAmount { type Output = Self; fn neg(self) -> Self::Output { Self::from_sat(self.to_sat().neg()) } } impl core::iter::Sum> for NumOpResult { fn sum(iter: I) -> Self where I: Iterator>, { iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (_, _) => R::Error(NumOpError {}), }) } } impl<'a> core::iter::Sum<&'a NumOpResult> for NumOpResult { fn sum(iter: I) -> Self where I: Iterator>, { iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (_, _) => R::Error(NumOpError {}), }) } } impl core::iter::Sum> for NumOpResult { fn sum(iter: I) -> Self where I: Iterator>, { iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (_, _) => R::Error(NumOpError {}), }) } } impl<'a> core::iter::Sum<&'a NumOpResult> for NumOpResult { fn sum(iter: I) -> Self where I: Iterator>, { iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (_, _) => R::Error(NumOpError {}), }) } } pub(in crate::amount) trait OptionExt { fn valid_or_error(self) -> NumOpResult; } impl OptionExt for Option { #[inline] fn valid_or_error(self) -> NumOpResult { match self { Some(amount) => R::Valid(amount), None => R::Error(NumOpError {}), } } } impl OptionExt for Option { #[inline] fn valid_or_error(self) -> NumOpResult { match self { Some(amount) => R::Valid(amount), None => R::Error(NumOpError {}), } } } /// An error occurred while doing a mathematical operation. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct NumOpError; impl fmt::Display for NumOpError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "a math operation on amounts gave an invalid numeric result") } } #[cfg(feature = "std")] impl std::error::Error for NumOpError {}