use std::{
fmt::{Debug, Display},
iter::Sum,
marker::PhantomData,
str::FromStr,
};
#[cfg(feature = "accounting")]
use crate::AccountingOps;
use crate::{
BaseMoney, BaseOps, Decimal, MoneyError, MoneyOps,
base::{Amount, DecimalNumber},
macros::dec,
parse::{
parse_code_locale_separator, parse_comma_thousands_separator,
parse_dot_thousands_separator, parse_symbol_comma_thousands_separator,
parse_symbol_dot_thousands_separator, parse_symbol_locale_separator,
},
};
use crate::{Currency, MoneyFormatter};
use rust_decimal::{MathematicalOps, prelude::FromPrimitive};
#[derive(Copy, PartialEq, Eq)]
pub struct Money<C: Currency> {
amount: Decimal,
_currency: PhantomData<C>,
}
impl<C> Money<C>
where
C: Currency,
{
#[inline]
pub fn from_decimal(amount: Decimal) -> Self {
Self {
amount,
_currency: PhantomData,
}
.round()
}
#[inline]
pub fn from_minor(minor_amount: i128) -> Result<Self, MoneyError> {
Ok(Self {
amount: Decimal::from_i128(minor_amount)
.ok_or(MoneyError::OverflowError)?
.checked_div(
dec!(10)
.checked_powu(C::MINOR_UNIT.into())
.ok_or(MoneyError::OverflowError)?,
)
.ok_or(MoneyError::OverflowError)?,
_currency: PhantomData,
}
.round())
}
pub fn from_code_comma_thousands(s: &str) -> Result<Self, MoneyError> {
let s = s.trim();
if let Some((currency_code, amount_str)) = parse_comma_thousands_separator(s) {
if currency_code != C::CODE {
return Err(MoneyError::CurrencyMismatchError(
currency_code.into(),
C::CODE.into(),
));
}
return Ok(Self::from_decimal(Decimal::from_str(&amount_str).map_err(
|err| MoneyError::ParseStrError(err.to_string().into()),
)?));
}
Err(MoneyError::ParseStrError(format!(
"failed parsing {}, use format: <CODE> <AMOUNT> where <CODE> is defined and <AMOUNT> is comma-separated thousands(optional) and dot-separated decimal",
s
).into()))
}
pub fn from_code_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::CurrencyMismatchError(
currency_code.into(),
C::CODE.into(),
));
}
return Ok(Self::from_decimal(Decimal::from_str(&amount_str).map_err(
|err| MoneyError::ParseStrError(err.to_string().into()),
)?));
}
Err(MoneyError::ParseStrError(format!(
"failed parsing {}, use format: <CODE> <AMOUNT> where <CODE> is defined and <AMOUNT> is dot-separated thousands(optional) and comma-separated decimal",
s
).into()))
}
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) {
if symbol != C::SYMBOL {
return Err(MoneyError::CurrencyMismatchError(
symbol.into(),
C::SYMBOL.into(),
));
}
return Ok(Self::from_decimal(Decimal::from_str(&amount_str).map_err(
|err| MoneyError::ParseStrError(err.to_string().into()),
)?));
}
Err(MoneyError::ParseStrError(format!(
"failed parsing {}, use format: <SYMBOL><AMOUNT> where <SYMBOL> is defined and <AMOUNT> is comma-separated thousands(optional) and dot-separated decimal",
s
).into()))
}
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) {
if symbol != C::SYMBOL {
return Err(MoneyError::CurrencyMismatchError(
symbol.into(),
C::SYMBOL.into(),
));
}
return Ok(Self::from_decimal(Decimal::from_str(&amount_str).map_err(
|err| MoneyError::ParseStrError(err.to_string().into()),
)?));
}
Err(MoneyError::ParseStrError(format!(
"failed parsing {}, use format: <SYMBOL><AMOUNT> where <SYMBOL> is defined and <AMOUNT> is dot-separated thousands(optional) and comma-separated decimal",
s
).into()))
}
pub fn from_code_locale_separator(s: &str) -> Result<Self, MoneyError> {
let s = s.trim();
if let Some((code, amount_str)) = parse_code_locale_separator::<C>(s) {
if code != C::CODE {
return Err(MoneyError::CurrencyMismatchError(
code.into(),
C::CODE.into(),
));
}
return Self::from_str(&amount_str)
.map_err(|err| MoneyError::ParseStrError(err.to_string().into()));
}
Err(MoneyError::ParseStrError(format!(
"failed parsing {}, use format: <CODE> <AMOUNT> where <CODE> is defined and <AMOUNT> is separated by locale separators",
s
).into()))
}
pub fn from_symbol_locale_separator(s: &str) -> Result<Self, MoneyError> {
let s = s.trim();
if let Some((symbol, amount_str)) = parse_symbol_locale_separator::<C>(s) {
if symbol != C::SYMBOL {
return Err(MoneyError::CurrencyMismatchError(
symbol.into(),
C::SYMBOL.into(),
));
}
return Self::from_str(&amount_str)
.map_err(|err| MoneyError::ParseStrError(err.to_string().into()));
}
Err(MoneyError::ParseStrError(format!(
"failed parsing {}, use format: <SYMBOL><AMOUNT> where <SYMBOL> is defined and <AMOUNT> is separated by locale separators",
s
).into()))
}
}
impl<C: Currency> Default for Money<C> {
fn default() -> Self {
Self {
amount: Decimal::default(),
_currency: PhantomData,
}
}
}
impl<C: Currency> Ord for Money<C>
where
C: Currency + PartialEq + Eq,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.amount.cmp(&other.amount)
}
}
impl<C> PartialOrd for Money<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 Money<C>
where
C: Currency,
{
fn get_decimal(&self) -> Option<Decimal> {
Some(self.amount())
}
}
impl<C> FromStr for Money<C>
where
C: Currency,
{
type Err = MoneyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let dec_num = Decimal::from_str(s).map_err(|err| {
MoneyError::ParseStrError(format!("failed parsing money from string: {}", err).into())
})?;
Ok(Self::from_decimal(dec_num))
}
}
impl<C: Currency> Clone for Money<C> {
fn clone(&self) -> Self {
Self {
amount: self.amount,
_currency: PhantomData,
}
}
}
impl<C> Display for Money<C>
where
C: Currency,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display())
}
}
impl<C> Debug for Money<C>
where
C: Currency,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Money({}, {})", C::CODE, self.amount)
}
}
impl<C: Currency> Sum for Money<C> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Money::default(), |acc, b| acc + b)
}
}
impl<'a, C: Currency> Sum<&'a Money<C>> for Money<C> {
fn sum<I: Iterator<Item = &'a Money<C>>>(iter: I) -> Self {
iter.fold(Money::default(), |acc, b| acc + b.clone())
}
}
impl<C> BaseMoney<C> for Money<C>
where
C: Currency,
{
#[inline]
fn new(amount: impl DecimalNumber) -> Result<Self, MoneyError> {
Ok(Self {
amount: amount.get_decimal().ok_or(MoneyError::OverflowError)?,
_currency: PhantomData,
}
.round())
}
#[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 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,
}
}
#[inline]
fn truncate(&self) -> Self {
Self::from_decimal(self.amount.trunc())
}
#[inline]
fn truncate_with(&self, scale: u32) -> Self {
Self::from_decimal(self.amount.trunc_with_scale(scale))
}
}
impl<C> BaseOps<C> for Money<C>
where
C: Currency,
{
#[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> MoneyFormatter<C> for Money<C> where C: Currency {}
#[cfg(feature = "accounting")]
impl<C> AccountingOps<C> for Money<C> where C: Currency {}
impl<C> MoneyOps<C> for Money<C> where C: Currency {}