use std::{fmt::Display, iter::Sum, marker::PhantomData, str::FromStr};
use crate::{
BaseMoney, BaseOps, Decimal, Money, MoneyError,
base::{Amount, DecimalNumber},
macros::dec,
parse::{
parse_comma_thousands_separator, parse_dot_thousands_separator,
parse_symbol_comma_thousands_separator, parse_symbol_dot_thousands_separator,
},
};
use crate::{Currency, CustomMoney};
use rust_decimal::{MathematicalOps, prelude::FromPrimitive, prelude::ToPrimitive};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawMoney<C: Currency> {
amount: Decimal,
_currency: PhantomData<C>,
}
impl<C> RawMoney<C>
where
C: Currency + Clone,
{
#[inline]
pub const fn from_decimal(amount: Decimal) -> Self {
Self {
amount,
_currency: PhantomData,
}
}
#[inline]
pub fn from_minor(minor_amount: i128) -> Result<Self, MoneyError> {
Ok(Self {
amount: Decimal::from_i128(minor_amount)
.ok_or(MoneyError::DecimalConversion)?
.checked_div(
dec!(10)
.checked_powu(C::MINOR_UNIT.into())
.ok_or(MoneyError::ArithmeticOverflow)?,
)
.ok_or(MoneyError::ArithmeticOverflow)?,
_currency: PhantomData,
})
}
#[inline]
pub fn finish(self) -> Money<C> {
Money::from_decimal(self.amount())
}
pub fn from_str_dot_thousands(s: &str) -> Result<Self, MoneyError> {
let s = s.trim();
if let Some((currency_code, amount_str)) = parse_dot_thousands_separator(s) {
if currency_code != C::CODE {
return Err(MoneyError::CurrencyMismatch);
}
return Ok(Self::from_decimal(
Decimal::from_str(&amount_str).map_err(|_| MoneyError::ParseStr)?,
));
}
Err(MoneyError::ParseStr)
}
pub fn from_symbol_comma_thousands(s: &str) -> Result<Self, MoneyError> {
let s = s.trim();
if let Some((symbol, amount_str)) = parse_symbol_comma_thousands_separator::<C>(s)
&& symbol == C::SYMBOL
{
return Ok(Self::from_decimal(
Decimal::from_str(&amount_str).map_err(|_| MoneyError::ParseStr)?,
));
}
Err(MoneyError::ParseStr)
}
pub fn from_symbol_dot_thousands(s: &str) -> Result<Self, MoneyError> {
let s = s.trim();
if let Some((symbol, amount_str)) = parse_symbol_dot_thousands_separator::<C>(s)
&& symbol == C::SYMBOL
{
return Ok(Self::from_decimal(
Decimal::from_str(&amount_str).map_err(|_| MoneyError::ParseStr)?,
));
}
Err(MoneyError::ParseStr)
}
}
impl<C: Currency> Default for RawMoney<C> {
fn default() -> Self {
Self {
amount: Decimal::default(),
_currency: PhantomData,
}
}
}
impl<C: Currency> Ord for RawMoney<C>
where
C: Currency + PartialEq + Eq,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.amount.cmp(&other.amount)
}
}
impl<C> PartialOrd for RawMoney<C>
where
C: Currency + PartialEq + Eq,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<C> Amount<C> for RawMoney<C>
where
C: Currency + Clone,
{
fn get_decimal(&self) -> Option<Decimal> {
Some(self.amount())
}
}
impl<C> FromStr for RawMoney<C>
where
C: Currency + Clone,
{
type Err = MoneyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if let Some((currency_code, amount_str)) = parse_comma_thousands_separator(s) {
if currency_code != C::CODE {
return Err(MoneyError::CurrencyMismatch);
}
return Ok(Self::from_decimal(
Decimal::from_str(&amount_str).map_err(|_| MoneyError::ParseStr)?,
));
}
Err(MoneyError::ParseStr)
}
}
impl<C> Display for RawMoney<C>
where
C: Currency + Clone,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display())
}
}
impl<C: Currency + Clone> Sum for RawMoney<C> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(RawMoney::default(), |acc, b| acc + b)
}
}
impl<'a, C: Currency + Clone> Sum<&'a RawMoney<C>> for RawMoney<C> {
fn sum<I: Iterator<Item = &'a RawMoney<C>>>(iter: I) -> Self {
iter.fold(RawMoney::default(), |acc, b| acc + b.clone())
}
}
impl<C> BaseMoney<C> for RawMoney<C>
where
C: Currency + Clone,
{
#[inline]
fn new(amount: impl DecimalNumber) -> Result<Self, MoneyError> {
Ok(Self {
amount: amount.get_decimal().ok_or(MoneyError::DecimalConversion)?,
_currency: PhantomData,
})
}
#[inline]
fn amount(&self) -> Decimal {
self.amount
}
#[inline]
fn round(self) -> Self {
Self {
amount: self.amount().round_dp(C::MINOR_UNIT.into()),
_currency: PhantomData,
}
}
#[inline]
fn minor_amount(&self) -> Result<i128, MoneyError> {
self.amount()
.round_dp(C::MINOR_UNIT.into())
.checked_mul(
dec!(10)
.checked_powu(C::MINOR_UNIT.into())
.ok_or(MoneyError::ArithmeticOverflow)?,
)
.ok_or(MoneyError::ArithmeticOverflow)?
.to_i128()
.ok_or(MoneyError::DecimalConversion)
}
}
impl<C> BaseOps<C> for RawMoney<C>
where
C: Currency + Clone,
{
#[inline]
fn abs(&self) -> Self {
Self::from_decimal(self.amount.abs())
}
#[inline]
fn checked_add<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: Amount<C>,
{
Some(Self::from_decimal(
self.amount.checked_add(rhs.get_decimal()?)?,
))
}
#[inline]
fn checked_sub<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: Amount<C>,
{
Some(Self::from_decimal(
self.amount.checked_sub(rhs.get_decimal()?)?,
))
}
#[inline]
fn checked_mul<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: DecimalNumber,
{
Some(Self::from_decimal(
self.amount.checked_mul(rhs.get_decimal()?)?,
))
}
#[inline]
fn checked_div<RHS>(&self, rhs: RHS) -> Option<Self>
where
RHS: DecimalNumber,
{
Some(Self::from_decimal(
self.amount.checked_div(rhs.get_decimal()?)?,
))
}
}
impl<C> CustomMoney<C> for RawMoney<C>
where
C: Currency + Clone,
{
#[inline]
fn round_with(self, decimal_points: u32, strategy: crate::base::RoundingStrategy) -> Self {
Self {
amount: self
.amount
.round_dp_with_strategy(decimal_points, strategy.into()),
_currency: PhantomData,
}
}
}