1use crate::{Exponent, Money, Rates};
2
3pub trait Operation {
5 fn execute(self, rates: &Rates) -> Option<Money>;
7}
8
9pub struct Add<A: Operation, B: Operation>(pub A, pub B);
11pub struct Sub<A: Operation, B: Operation>(pub A, pub B);
13pub struct Mul<A: Operation>(pub A, pub Exponent);
15pub struct Div<A: Operation>(pub A, pub Exponent);
17
18impl<A: Operation, B: Operation> Operation for Add<A, B> {
19 fn execute(self, rates: &Rates) -> Option<Money> {
20 let money_a = self.0.execute(rates)?;
21 let money_b = self.1.execute(rates)?;
22
23 Some(Money::new(
24 money_a.amount + money_b.into_code(money_a.currency_code, rates)?.amount,
25 money_a.currency_code,
26 ))
27 }
28}
29
30impl<A: Operation, B: Operation> Operation for Sub<A, B> {
31 fn execute(self, rates: &Rates) -> Option<Money> {
32 let money_a = self.0.execute(rates)?;
33 let money_b = self.1.execute(rates)?;
34
35 Some(Money::new(
36 money_a.amount - money_b.into_code(money_a.currency_code, rates)?.amount,
37 money_a.currency_code,
38 ))
39 }
40}
41
42impl<A: Operation> Operation for Mul<A> {
43 fn execute(self, rates: &Rates) -> Option<Money> {
44 let exponent = &self.1;
45 let money_a = self.0.execute(rates)?;
46
47 Some(Money::new(
48 money_a.amount * exponent.amount / 10i128.pow(u32::from(exponent.exponent)).into(),
49 money_a.currency_code,
50 ))
51 }
52}
53
54impl<A: Operation> Operation for Div<A> {
55 fn execute(self, rates: &Rates) -> Option<Money> {
56 let exponent = &self.1;
57 let money_a = self.0.execute(rates)?;
58
59 Some(Money::new(
60 money_a.amount * 10i128.pow(u32::from(exponent.exponent)).into() / exponent.amount,
61 money_a.currency_code,
62 ))
63 }
64}
65
66impl<O: Operation, _A: Operation, _B: Operation> std::ops::Add<O> for Add<_A, _B> {
68 type Output = crate::ops::Add<Self, O>;
69 fn add(self, other: O) -> Self::Output {
70 crate::ops::Add(self, other)
71 }
72}
73
74impl<O: Operation, _A: Operation, _B: Operation> std::ops::Sub<O> for Add<_A, _B> {
75 type Output = crate::ops::Sub<Self, O>;
76 fn sub(self, other: O) -> Self::Output {
77 crate::ops::Sub(self, other)
78 }
79}
80
81impl<_A: Operation, _B: Operation> std::ops::Mul<Exponent> for Add<_A, _B> {
82 type Output = crate::ops::Mul<Self>;
83 fn mul(self, exp: Exponent) -> Self::Output {
84 crate::ops::Mul(self, exp)
85 }
86}
87
88impl<_A: Operation, _B: Operation> std::ops::Div<Exponent> for Add<_A, _B> {
89 type Output = crate::ops::Div<Self>;
90 fn div(self, exp: Exponent) -> Self::Output {
91 crate::ops::Div(self, exp)
92 }
93}
94
95impl<O: Operation, _A: Operation, _B: Operation> std::ops::Add<O> for Sub<_A, _B> {
97 type Output = crate::ops::Add<Self, O>;
98 fn add(self, other: O) -> Self::Output {
99 crate::ops::Add(self, other)
100 }
101}
102
103impl<O: Operation, _A: Operation, _B: Operation> std::ops::Sub<O> for Sub<_A, _B> {
104 type Output = crate::ops::Sub<Self, O>;
105 fn sub(self, other: O) -> Self::Output {
106 crate::ops::Sub(self, other)
107 }
108}
109
110impl<_A: Operation, _B: Operation> std::ops::Mul<Exponent> for Sub<_A, _B> {
111 type Output = crate::ops::Mul<Self>;
112 fn mul(self, exp: Exponent) -> Self::Output {
113 crate::ops::Mul(self, exp)
114 }
115}
116
117impl<_A: Operation, _B: Operation> std::ops::Div<Exponent> for Sub<_A, _B> {
118 type Output = crate::ops::Div<Self>;
119 fn div(self, exp: Exponent) -> Self::Output {
120 crate::ops::Div(self, exp)
121 }
122}
123
124impl<O: Operation, _A: Operation> std::ops::Add<O> for Mul<_A> {
126 type Output = crate::ops::Add<Self, O>;
127 fn add(self, other: O) -> Self::Output {
128 crate::ops::Add(self, other)
129 }
130}
131
132impl<O: Operation, _A: Operation> std::ops::Sub<O> for Mul<_A> {
133 type Output = crate::ops::Sub<Self, O>;
134 fn sub(self, other: O) -> Self::Output {
135 crate::ops::Sub(self, other)
136 }
137}
138
139impl<_A: Operation> std::ops::Mul<Exponent> for Mul<_A> {
140 type Output = crate::ops::Mul<Self>;
141 fn mul(self, exp: Exponent) -> Self::Output {
142 crate::ops::Mul(self, exp)
143 }
144}
145
146impl<_A: Operation> std::ops::Div<Exponent> for Mul<_A> {
147 type Output = crate::ops::Div<Self>;
148 fn div(self, exp: Exponent) -> Self::Output {
149 crate::ops::Div(self, exp)
150 }
151}
152
153impl<O: Operation, _A: Operation> std::ops::Add<O> for Div<_A> {
155 type Output = crate::ops::Add<Self, O>;
156 fn add(self, other: O) -> Self::Output {
157 crate::ops::Add(self, other)
158 }
159}
160
161impl<O: Operation, _A: Operation> std::ops::Sub<O> for Div<_A> {
162 type Output = crate::ops::Sub<Self, O>;
163 fn sub(self, other: O) -> Self::Output {
164 crate::ops::Sub(self, other)
165 }
166}
167
168impl<_A: Operation> std::ops::Mul<Exponent> for Div<_A> {
169 type Output = crate::ops::Mul<Self>;
170 fn mul(self, exp: Exponent) -> Self::Output {
171 crate::ops::Mul(self, exp)
172 }
173}
174
175impl<_A: Operation> std::ops::Div<Exponent> for Div<_A> {
176 type Output = crate::ops::Div<Self>;
177 fn div(self, exp: Exponent) -> Self::Output {
178 crate::ops::Div(self, exp)
179 }
180}
181
182impl Operation for Money {
185 fn execute(self, _rates: &Rates) -> Option<Money> {
186 Some(self)
187 }
188}
189
190impl<O: Operation> std::ops::Add<O> for Money {
192 type Output = crate::ops::Add<Self, O>;
193 fn add(self, other: O) -> Self::Output {
194 crate::ops::Add(self, other)
195 }
196}
197
198impl<O: Operation> std::ops::Sub<O> for Money {
199 type Output = crate::ops::Sub<Self, O>;
200 fn sub(self, other: O) -> Self::Output {
201 crate::ops::Sub(self, other)
202 }
203}
204
205impl std::ops::Mul<Exponent> for Money {
206 type Output = crate::ops::Mul<Self>;
207 fn mul(self, exp: Exponent) -> Self::Output {
208 crate::ops::Mul(self, exp)
209 }
210}
211
212impl std::ops::Div<Exponent> for Money {
213 type Output = crate::ops::Div<Self>;
214 fn div(self, exp: Exponent) -> Self::Output {
215 crate::ops::Div(self, exp)
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use crate::rates;
222 use crate::{Exponent, Money, Operation};
223 use std::convert::TryInto;
224
225 #[test]
226 fn test_add_same_code_operation() {
227 let money1 = Money::new(1_000_000.into(), "USD".try_into().unwrap());
228 let money2 = Money::new(2_000_001.into(), "USD".try_into().unwrap());
229 let rates = rates();
230
231 assert_eq!(
232 Some(Money::new(3_000_001.into(), "USD".try_into().unwrap())),
233 (money1 + money2).execute(&rates)
234 );
235 }
236
237 #[test]
238 fn test_add_operation() {
239 let money1 = Money::with_str_code(1_000_010.into(), "GBP").unwrap();
241 let money2 = Money::with_str_code(1_500_015.into(), "USD").unwrap();
242 let rates = rates();
243
244 assert_eq!(
245 Money::with_str_code(2_000_020.into(), "GBP"),
246 (money1 + money2).execute(&rates)
247 );
248 }
249
250 #[test]
251 fn test_add_negative_operation() {
252 let money1 = Money::with_str_code(1_000_010.into(), "GBP").unwrap();
254 let money2 = Money::with_str_code((-1_500_015).into(), "USD").unwrap();
255 let rates = rates();
256
257 assert_eq!(
258 Money::with_str_code(0.into(), "GBP"),
259 (money1 + money2).execute(&rates)
260 );
261 }
262
263 #[test]
264 fn test_sub_operation() {
265 let money1 = Money::with_str_code(1_000_010.into(), "GBP").unwrap();
267 let money2 = Money::with_str_code(1_500_015.into(), "USD").unwrap();
268 let rates = rates();
269
270 assert_eq!(
271 Money::with_str_code(0.into(), "GBP"),
272 (money1 - money2).execute(&rates)
273 );
274 }
275
276 #[test]
277 fn test_sub_negative_operation() {
278 let money1 = Money::with_str_code(1_000_010.into(), "GBP").unwrap();
280 let money2 = Money::with_str_code((-1_500_015).into(), "USD").unwrap();
281 let rates = rates();
282
283 assert_eq!(
284 Money::with_str_code(2_000_020.into(), "GBP"),
285 (money1 - money2).execute(&rates)
286 );
287 }
288
289 #[test]
290 fn test_mul_operation() {
291 let money = Money::with_str_code(1_000_001.into(), "USD").unwrap();
292
293 assert_eq!(
294 (money * Exponent::new(1000.into(), 2)).execute(&rates()),
295 Money::with_str_code(10_000_010.into(), "USD")
296 );
297
298 assert_eq!(
299 (money * Exponent::new(1000.into(), 4)).execute(&rates()),
300 Money::with_str_code(100_000.into(), "USD")
301 );
302 }
303
304 #[test]
305 fn test_mul_negative_operation() {
306 let money = Money::with_str_code((-1_000_001).into(), "USD").unwrap();
307
308 assert_eq!(
309 (money * Exponent::new(1000.into(), 2)).execute(&rates()),
310 Money::with_str_code((-10_000_010).into(), "USD")
311 );
312
313 assert_eq!(
314 (money * Exponent::new(1000.into(), 4)).execute(&rates()),
315 Money::with_str_code((-100_000).into(), "USD")
316 );
317 }
318
319 #[test]
320 fn test_div_operation() {
321 let money = Money::with_str_code(1_000_001.into(), "USD").unwrap();
322
323 assert_eq!(
324 (money / Exponent::new(1000.into(), 2)).execute(&rates()),
325 Money::with_str_code(100_000.into(), "USD")
326 );
327
328 assert_eq!(
329 (money / Exponent::new(1000.into(), 4)).execute(&rates()),
330 Money::with_str_code(10_000_010.into(), "USD")
331 );
332 }
333
334 #[test]
335 fn test_div_negative_operation() {
336 let money = Money::with_str_code((-1_000_001).into(), "USD").unwrap();
337
338 assert_eq!(
339 (money / Exponent::new(1000.into(), 2)).execute(&rates()),
340 Money::with_str_code((-100_000).into(), "USD")
341 );
342
343 assert_eq!(
344 (money / Exponent::new(1000.into(), 4)).execute(&rates()),
345 Money::with_str_code((-10_000_010).into(), "USD")
346 );
347 }
348
349 #[test]
350 fn test_money_operation() {
351 let money = Money::with_str_code(1_000_000.into(), "USD").unwrap();
352 let rates = rates();
353
354 assert_eq!(money.execute(&rates), Some(money));
355 }
356
357 #[test]
358 fn test_long_add_and_sub_chain() {
359 let money1 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
360 let money2 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
361 let money3 = Money::with_str_code(2_000_000.into(), "USD").unwrap();
362 let money4 = Money::with_str_code(2_000_000.into(), "USD").unwrap();
363 let money5 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
364 let money6 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
365 let money7 = Money::with_str_code(2_000_000.into(), "USD").unwrap();
366
367 let result =
368 (money1 + money2 - money3 + money4 + money5 - money6 - money7).execute(&rates());
369
370 assert_eq!(result, Money::with_str_code(0.into(), "USD"))
371 }
372
373 #[test]
374 fn test_long_add_and_sub_chain_with_negative_outcome() {
375 let money1 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
376 let money2 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
377 let money3 = Money::with_str_code(2_000_000.into(), "USD").unwrap();
378 let money4 = Money::with_str_code(2_000_000.into(), "USD").unwrap();
379 let money5 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
380 let money6 = Money::with_str_code(1_000_000.into(), "USD").unwrap();
381 let money7 = Money::with_str_code(3_000_000.into(), "USD").unwrap();
382
383 let result =
384 (money1 + money2 - money3 + money4 + money5 - money6 - money7).execute(&rates());
385
386 assert_eq!(result, Money::with_str_code((-1_000_000).into(), "USD"))
387 }
388}