1use std::fmt;
2use std::str::FromStr;
3
4use crate::currency::*;
5use crate::Error;
6
7#[derive(PartialEq, Eq, Clone)]
8pub struct Money {
9 amount: i64,
10 currency: &'static Currency,
11 is_negative: bool,
12}
13
14#[macro_export]
15macro_rules! money {
16 ($x:expr, $y:expr) => {
17 Money::from_string($x.to_string(), $y.to_string()).unwrap();
18 };
19}
20
21impl Money {
22 pub fn from_string(s: String, currency: String) -> Result<Money, Error> {
23 let currency = Currency::from_string(currency).unwrap_or(Currency::find("USD")?);
24 let mut amount: i64 = 0;
25 let mut is_negative: bool = false;
26
27 if s != "" {
28 let mut decimal_found: bool = false;
29 let mut decimal_fill: u8 = 0;
30 let mut decimal_places: u8 = 0;
31
32 for char in s.chars() {
33 if char == '-' {
34 is_negative = true;
35 continue;
36 }
37 if char == '.' {
38 decimal_found = true;
39 continue;
40 }
41
42 let num = char.to_digit(10).ok_or(Error::InvalidAmount)? as i64;
43
44 if decimal_found {
45 decimal_fill += 1;
46 decimal_places += 1;
47
48 if decimal_places == 3 {
49 if num > 4 {
50 amount = amount.checked_add(1).ok_or(Error::InvalidAmount)?;
52 }
53 decimal_places -= 1;
54 break;
55 }
56 }
57
58 amount = amount.checked_mul(10).ok_or(Error::InvalidAmount)?;
60
61 amount = amount.checked_add(num).ok_or(Error::InvalidAmount)?;
63 }
64
65 if !decimal_found {
66 decimal_fill = 2;
67 }
68
69 if decimal_places == 2 {
70 decimal_fill = 0;
71 }
72
73 if decimal_fill > 0 {
74 loop {
75 amount *= 10;
76 decimal_fill -= 1;
77
78 if decimal_fill == 0 {
79 break;
80 }
81 }
82 }
83 }
84
85 Ok(Money {
86 amount,
87 currency,
88 is_negative,
89 })
90 }
91}
92
93impl FromStr for Money {
94 type Err = Error;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 Money::from_string(s.to_string(), "".to_string())
98 }
99}
100
101impl fmt::Display for Money {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 let mut s = format!("{:03}", self.amount);
104
105 if self.amount == 0 {
106 s = format!("{}", self.amount)
107 } else {
108 s.insert(s.len() - 2, '.');
109
110 let mut remainder: usize = s.len() - 3;
111 let mut position: usize = s.len() - 3;
112
113 loop {
114 if remainder <= 3 {
115 break;
116 }
117
118 position -= 3;
119
120 s.insert(position, ',');
121
122 remainder = position;
123 }
124
125 if self.is_negative {
126 s.insert(0, '-');
127 }
128
129 if self.currency.symbol != "" {
130 let fill = f.fill();
131 if let Some(width) = f.width() {
132 for _ in 0..width {
133 s.insert(0, fill)
134 }
135 }
136 s = format!("{}{}", self.currency.symbol, s)
137 }
138 }
139
140 write!(f, "{}", s.to_string())
141 }
142}
143
144impl fmt::Debug for Money {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 fmt::Display::fmt(self, f)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_create_from_string() {
156 let money: Money = "".parse().unwrap();
157 assert_eq!("0", money.to_string());
158
159 let money: Money = "0".parse().unwrap();
160 assert_eq!("0", money.to_string());
161
162 let money: Money = "0.0".parse().unwrap();
163 assert_eq!("0", money.to_string());
164
165 let money: Money = "0.00".parse().unwrap();
166 assert_eq!("0", money.to_string());
167
168 let money: Money = "111".parse().unwrap();
169 assert_eq!("$111.00", money.to_string());
170
171 let money: Money = "111.0".parse().unwrap();
172 assert_eq!("$111.00", money.to_string());
173
174 let money: Money = "111.1".parse().unwrap();
175 assert_eq!("$111.10", money.to_string());
176
177 let money: Money = "111.01".parse().unwrap();
178 assert_eq!("$111.01", money.to_string());
179
180 let money: Money = "112.00".parse().unwrap();
181 assert_eq!("$112.00", money.to_string());
182
183 let money = money!("19.9999", "USD");
184 assert_eq!("$20.00", format!("{}", money));
185
186 let money: Money = money!("20.00", "USD");
187 assert_eq!("$20.00", money.to_string())
188 }
189
190 #[test]
191 fn test_failing_creating_from_string() {
192 let money = "111f.05f".parse::<Money>();
193 assert_eq!(Error::InvalidAmount, money.unwrap_err());
194 }
195
196 #[test]
197 fn money_fmt_separates_digits() {
198 let usd = money!(0, "USD"); let expected_usd_fmt = "0";
200 assert_eq!(format!("{}", usd), expected_usd_fmt);
201 }
202
203 #[test]
204 fn money_format_rounds_exponent() {
205 let money = money!("19.9999", "USD");
207 assert_eq!("$20.00", format!("{}", money));
208
209 let money = money!("29.111", "USD");
211 assert_eq!("$29.11", format!("{}", money));
212
213 let money: Money = "11123.0154".parse().unwrap();
214 assert_eq!("$11,123.02", money.to_string());
215
216 let money: Money = "1112345.0154".parse().unwrap();
217 assert_eq!("$1,112,345.02", money.to_string());
218 }
219
220 #[test]
221 fn money_format_padding() {
222 let money = money!("20.00", "USD");
223 assert_eq!("$ 20.00", format!("{: >1}", money));
224 }
225
226 #[test]
227 fn money_from_float() {
228 let money = money!(20, "USD");
229 assert_eq!("$ 20.00", format!("{: >1}", money));
230
231 let money = money!(20.10, "USD");
232 assert_eq!("$ 20.10", format!("{: >1}", money));
233
234 let money = money!(20.105, "USD");
235 assert_eq!("$ 20.11", format!("{: >1}", money));
236 }
237
238 #[test]
239 fn money_allow_negative() {
240 let money = money!(-20, "USD");
241 assert_eq!("$ -20.00", format!("{: >1}", money));
242
243 let money: Money = "-20".parse().unwrap();
244 assert_eq!("$-20.00", money.to_string());
245 }
246}