monet/
ops.rs

1use crate::{Exponent, Money, Rates};
2
3/// A generic operation trait
4pub trait Operation {
5    /// Execute this operation agains some defined rates.
6    fn execute(self, rates: &Rates) -> Option<Money>;
7}
8
9/// An operation adding two currencies. The output has same currency code as `A`.
10pub struct Add<A: Operation, B: Operation>(pub A, pub B);
11/// Operation subtracting two currencies. The output has same currency code as `A`.
12pub struct Sub<A: Operation, B: Operation>(pub A, pub B);
13/// Operation multiplying a money by an amount. The output has same currency code as `A`.
14pub struct Mul<A: Operation>(pub A, pub Exponent);
15/// Operation dividing a money by an amount. The output has same currency code as `A`.
16pub 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
66// Impl chaining for Add
67impl<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
95// Impl chaining for Sub
96impl<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
124// Impl chaining for Mul
125impl<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
153// Impl chaining for Div
154impl<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
182// Impl Operation for money, to allow easier chaining
183
184impl Operation for Money {
185    fn execute(self, _rates: &Rates) -> Option<Money> {
186        Some(self)
187    }
188}
189
190// Impl chaining for Money
191impl<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        // Two equal amounts of money
240        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        // Two equal amounts of money
253        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        // Two equal amounts of money
266        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        // Two equal amounts of money
279        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}