use std::fmt;
use std::str::FromStr;
use crate::currency::*;
use crate::Error;
#[derive(PartialEq, Eq, Clone)]
pub struct Money {
amount: i64,
currency: &'static Currency,
is_negative: bool,
}
#[macro_export]
macro_rules! money {
($x:expr, $y:expr) => {
Money::from_string($x.to_string(), $y.to_string()).unwrap();
};
}
impl Money {
pub fn from_string(s: String, currency: String) -> Result<Money, Error> {
let currency = Currency::from_string(currency).unwrap_or(Currency::find("USD")?);
let mut amount: i64 = 0;
let mut is_negative: bool = false;
if s != "" {
let mut decimal_found: bool = false;
let mut decimal_fill: u8 = 0;
let mut decimal_places: u8 = 0;
for char in s.chars() {
if char == '-' {
is_negative = true;
continue;
}
if char == '.' {
decimal_found = true;
continue;
}
let num = char.to_digit(10).ok_or(Error::InvalidAmount)? as i64;
if decimal_found {
decimal_fill += 1;
decimal_places += 1;
if decimal_places == 3 {
if num > 4 {
amount = amount.checked_add(1).ok_or(Error::InvalidAmount)?;
}
decimal_places -= 1;
break;
}
}
amount = amount.checked_mul(10).ok_or(Error::InvalidAmount)?;
amount = amount.checked_add(num).ok_or(Error::InvalidAmount)?;
}
if !decimal_found {
decimal_fill = 2;
}
if decimal_places == 2 {
decimal_fill = 0;
}
if decimal_fill > 0 {
loop {
amount *= 10;
decimal_fill -= 1;
if decimal_fill == 0 {
break;
}
}
}
}
Ok(Money {
amount,
currency,
is_negative,
})
}
}
impl FromStr for Money {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Money::from_string(s.to_string(), "".to_string())
}
}
impl fmt::Display for Money {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = format!("{:03}", self.amount);
if self.amount == 0 {
s = format!("{}", self.amount)
} else {
s.insert(s.len() - 2, '.');
let mut remainder: usize = s.len() - 3;
let mut position: usize = s.len() - 3;
loop {
if remainder <= 3 {
break;
}
position -= 3;
s.insert(position, ',');
remainder = position;
}
if self.is_negative {
s.insert(0, '-');
}
if self.currency.symbol != "" {
let fill = f.fill();
if let Some(width) = f.width() {
for _ in 0..width {
s.insert(0, fill)
}
}
s = format!("{}{}", self.currency.symbol, s)
}
}
write!(f, "{}", s.to_string())
}
}
impl fmt::Debug for Money {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_from_string() {
let money: Money = "".parse().unwrap();
assert_eq!("0", money.to_string());
let money: Money = "0".parse().unwrap();
assert_eq!("0", money.to_string());
let money: Money = "0.0".parse().unwrap();
assert_eq!("0", money.to_string());
let money: Money = "0.00".parse().unwrap();
assert_eq!("0", money.to_string());
let money: Money = "111".parse().unwrap();
assert_eq!("$111.00", money.to_string());
let money: Money = "111.0".parse().unwrap();
assert_eq!("$111.00", money.to_string());
let money: Money = "111.1".parse().unwrap();
assert_eq!("$111.10", money.to_string());
let money: Money = "111.01".parse().unwrap();
assert_eq!("$111.01", money.to_string());
let money: Money = "112.00".parse().unwrap();
assert_eq!("$112.00", money.to_string());
let money = money!("19.9999", "USD");
assert_eq!("$20.00", format!("{}", money));
let money: Money = money!("20.00", "USD");
assert_eq!("$20.00", money.to_string())
}
#[test]
fn test_failing_creating_from_string() {
let money = "111f.05f".parse::<Money>();
assert_eq!(Error::InvalidAmount, money.unwrap_err());
}
#[test]
fn money_fmt_separates_digits() {
let usd = money!(0, "USD"); let expected_usd_fmt = "0";
assert_eq!(format!("{}", usd), expected_usd_fmt);
}
#[test]
fn money_format_rounds_exponent() {
let money = money!("19.9999", "USD");
assert_eq!("$20.00", format!("{}", money));
let money = money!("29.111", "USD");
assert_eq!("$29.11", format!("{}", money));
let money: Money = "11123.0154".parse().unwrap();
assert_eq!("$11,123.02", money.to_string());
let money: Money = "1112345.0154".parse().unwrap();
assert_eq!("$1,112,345.02", money.to_string());
}
#[test]
fn money_format_padding() {
let money = money!("20.00", "USD");
assert_eq!("$ 20.00", format!("{: >1}", money));
}
#[test]
fn money_from_float() {
let money = money!(20, "USD");
assert_eq!("$ 20.00", format!("{: >1}", money));
let money = money!(20.10, "USD");
assert_eq!("$ 20.10", format!("{: >1}", money));
let money = money!(20.105, "USD");
assert_eq!("$ 20.11", format!("{: >1}", money));
}
#[test]
fn money_allow_negative() {
let money = money!(-20, "USD");
assert_eq!("$ -20.00", format!("{: >1}", money));
let money: Money = "-20".parse().unwrap();
assert_eq!("$-20.00", money.to_string());
}
}