use std::f64;
use rust_decimal::prelude::ToPrimitive;
use serde::{Deserialize, Serialize};
#[derive(thiserror::Error, Debug)]
pub enum CryptoAmountError {
#[error("NegativeAmount")]
NegativeAmount,
#[error("Decimal: {0}")]
Decimal(rust_decimal::Error),
}
impl From<rust_decimal::Error> for CryptoAmountError {
fn from(value: rust_decimal::Error) -> Self {
Self::Decimal(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct CryptoAmount(rust_decimal::Decimal);
impl CryptoAmount {
pub const ZERO: Self = Self(rust_decimal::Decimal::ZERO);
pub fn inner(&self) -> rust_decimal::Decimal {
self.0
}
pub const unsafe fn new_unchecked(value: rust_decimal::Decimal) -> Self {
Self(value)
}
pub fn to_f64_lossy(&self) -> f64 {
if let Some(v) = self.0.to_f64() {
return v;
}
log::error!(
"[unreachable] Could not convert CryptoAmount to f64! {:?}. Please file a bug report!",
self
);
f64::NAN
}
}
impl From<u64> for CryptoAmount {
fn from(value: u64) -> Self {
Self(rust_decimal::Decimal::from(value))
}
}
impl TryFrom<f64> for CryptoAmount {
type Error = CryptoAmountError;
fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
Self::try_from(rust_decimal::Decimal::try_from(value)?)
}
}
impl TryFrom<rust_decimal::Decimal> for CryptoAmount {
type Error = CryptoAmountError;
fn try_from(value: rust_decimal::Decimal) -> std::result::Result<Self, Self::Error> {
if value < rust_decimal::Decimal::ZERO {
return Err(CryptoAmountError::NegativeAmount);
}
Ok(Self(value))
}
}
impl TryFrom<api_types::api::decimal::Decimal> for CryptoAmount {
type Error = CryptoAmountError;
fn try_from(value: api_types::api::decimal::Decimal) -> std::result::Result<Self, Self::Error> {
Self::try_from(value.0)
}
}
impl From<CryptoAmount> for api_types::api::decimal::Decimal {
fn from(val: CryptoAmount) -> Self {
Self(val.0)
}
}
impl TryFrom<CryptoAmount> for f64 {
type Error = CryptoAmountError;
fn try_from(value: CryptoAmount) -> std::result::Result<Self, Self::Error> {
Ok(value.0.try_into()?)
}
}
impl std::ops::Add for CryptoAmount {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl std::ops::Div for CryptoAmount {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
Self(self.0 / rhs.0)
}
}
impl std::ops::Mul for CryptoAmount {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self(self.0 * rhs.0)
}
}
#[cfg(test)]
mod test {
use super::*;
use rust_decimal_macros::dec;
#[rstest::rstest]
#[case(dec!(0.0), Some(CryptoAmount(dec!(0.0))))]
#[case(dec!(-0.0), Some(CryptoAmount(dec!(-0.0))))]
#[case(dec!(-1.0), None)]
#[case(dec!(-10.5), None)]
#[case(dec!(1.0), Some(CryptoAmount(dec!(1.0))))]
#[case(dec!(10.0), Some(CryptoAmount(dec!(10.0))))]
fn test_try_from_non_zero_dec(#[case] value: rust_decimal::Decimal, #[case] expected_value: Option<CryptoAmount>) {
let amount = CryptoAmount::try_from(value);
match (amount, expected_value) {
(Ok(amount), Some(expected)) => assert_eq!(amount, expected),
(Err(error), None) => assert!(matches!(error, CryptoAmountError::NegativeAmount)),
(amount, expected) => panic!("expected {expected:?} but got {amount:?} for {value}"),
}
}
#[rstest::rstest]
#[case(0.0, Some(CryptoAmount(dec!(0.0))))]
#[case(-0.0, Some(CryptoAmount(dec!(0.0))))] #[case(-1.0, None)]
#[case(-10.5, None)]
#[case(1.0, Some(CryptoAmount(dec!(1.0))))]
#[case(10.0, Some(CryptoAmount(dec!(10.0))))]
fn test_try_from_non_zero_f64(#[case] value: f64, #[case] expected_value: Option<CryptoAmount>) {
let amount = CryptoAmount::try_from(value);
match (amount, expected_value) {
(Ok(amount), Some(expected)) => assert_eq!(amount, expected),
(Err(error), None) => assert!(matches!(error, CryptoAmountError::NegativeAmount)),
(amount, expected) => panic!("expected {expected:?} but got {amount:?} for {value}"),
}
}
}