postgres_money/
lib.rs

1#![doc(html_root_url = "https://docs.rs/postgres_money/0.4.1")]
2// Copyright 2024 Thomas Brigham
3// Licensed under the  MIT license <LICENSE or http://opensource.org/licenses/MIT>.
4// This file may not be copied, modified, or distributed except according to those terms.
5
6//! # Dependencies
7//!
8//! By default, this crate depends on the `regex` crate.
9//!
10//! To activate JSON serialization via the `serde` crate, use syntax like:
11//! ```toml
12//! [dependencies]
13//! postgres_money = { version = "0.4.1", features = ["serde", "sql"] }
14//! ```
15//!
16//! Visit the docs for [Money](struct.Money.html) for more info.
17
18mod error;
19mod parser;
20
21#[cfg(feature = "sql")]
22mod sql_impl;
23
24use error::Error;
25use std::ops::{Add, Div, Mul, Sub};
26use std::{fmt, str};
27
28/// Representation of the Postgres 'money' type
29#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub struct Money(Inner);
32type Inner = i64;
33
34impl Money {
35    const MIN_INNER: Inner = std::i64::MIN;
36    const MAX_INNER: Inner = std::i64::MAX;
37
38    /// Minimum allowable value for Money
39    pub const fn min() -> Money {
40        Money(Money::MIN_INNER)
41    }
42
43    /// Maximum allowable value for Money
44    pub const fn max() -> Money {
45        Money(Money::MAX_INNER)
46    }
47
48    /// Instantiate Money as zero
49    pub const fn none() -> Money {
50        Money(0)
51    }
52
53    /// Expose the wrapped i64 value
54    pub const fn inner(&self) -> Inner {
55        self.0
56    }
57
58    fn dollars(&self) -> String {
59        format!("{}", (self.0 / 100).abs())
60    }
61
62    fn cents(&self) -> String {
63        let n = (self.0 % 100).abs();
64        let mut zero_pad = "";
65        if n < 10 {
66            zero_pad = "0"
67        }
68        format!("{}{}", zero_pad, n)
69    }
70
71    fn sign(&self) -> &str {
72        if self.inner() < 0 {
73            "-"
74        } else {
75            ""
76        }
77    }
78}
79
80impl fmt::Debug for Money {
81    #[inline]
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}", self)
84    }
85}
86
87impl fmt::Display for Money {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(f, "{}${}.{}", self.sign(), self.dollars(), self.cents())
90    }
91}
92
93impl str::FromStr for Money {
94    type Err = Error;
95
96    fn from_str(money_str: &str) -> Result<Self, Self::Err> {
97        Money::parse_str(money_str)
98    }
99}
100
101impl Default for Money {
102    #[inline]
103    fn default() -> Self {
104        Money::none()
105    }
106}
107
108macro_rules! derive_op_trait_from_inner {
109    (impl $imp:ident, $method:ident) => {
110        impl $imp for Money {
111            type Output = Self;
112
113            fn $method(self, rhs: Money) -> Self::Output {
114                Money(self.inner().$method(rhs.inner()))
115            }
116        }
117    };
118}
119
120macro_rules! derive_trait_for_money_with_type {
121    (impl $imp:ident with $t:ty, $method:ident) => {
122        impl $imp<$t> for Money {
123            type Output = Self;
124
125            fn $method(self, rhs: $t) -> Self::Output {
126                Money(self.inner().$method(rhs as i64))
127            }
128        }
129    };
130}
131
132macro_rules! derive_trait_for_type_with_money {
133    (impl $imp:ident with $t:ty, $method:ident) => {
134        impl $imp<Money> for $t {
135            type Output = Money;
136
137            fn $method(self, rhs: Money) -> Self::Output {
138                Money((self as i64).$method(rhs.inner()))
139            }
140        }
141    };
142}
143
144macro_rules! add_mul_impl {
145    ($($t:ty)+) => ($(
146        derive_trait_for_money_with_type! { impl Mul with $t, mul }
147        derive_trait_for_type_with_money! { impl Mul with $t, mul }
148    )+)
149}
150
151macro_rules! add_div_impl {
152    ($($t:ty)+) => ($(
153        derive_trait_for_money_with_type! { impl Div with $t, div }
154    )+)
155}
156
157derive_op_trait_from_inner!(impl Add, add);
158derive_op_trait_from_inner!(impl Sub, sub);
159add_mul_impl! { i64 i32 i16 i8 u32 u16 u8 f64 f32 }
160add_div_impl! { i64 i32 i16 i8 u32 u16 u8 }
161
162impl Div<f64> for Money {
163    type Output = Self;
164
165    fn div(self, rhs: f64) -> Self::Output {
166        let inner = self.inner() as f64 / rhs;
167        Money(inner.round() as i64)
168    }
169}
170
171impl Div<f32> for Money {
172    type Output = Self;
173
174    fn div(self, rhs: f32) -> Self::Output {
175        let inner = self.inner() as f32 / rhs;
176        Money(inner.round() as i64)
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use crate::Money;
183
184    macro_rules! gen_mul_int_tests {
185        ($t:ty, $success:ident, $success_reversed:ident, $of_max:ident, $of_min:ident) => {
186            #[test]
187            fn $success() {
188                assert_eq!(Money(7) * 3 as $t, Money(21))
189            }
190
191            #[test]
192            fn $success_reversed() {
193                assert_eq!(3 as $t * Money(7), Money(21))
194            }
195
196            #[test]
197            #[should_panic]
198            #[allow(unused_must_use)]
199            fn $of_max() {
200                Money::max() * 100 as $t;
201            }
202
203            #[test]
204            #[should_panic]
205            #[allow(unused_must_use)]
206            fn $of_min() {
207                Money::min() * 100 as $t;
208            }
209        };
210    }
211
212    macro_rules! gen_div_int_tests {
213        ($t:ty, $success:ident, $truncates:ident) => {
214            #[test]
215            fn $success() {
216                assert_eq!(Money(21) / 3 as $t, Money(7))
217            }
218
219            #[test]
220            fn $truncates() {
221                assert_eq!(Money(21) / 2 as $t, Money(10))
222            }
223        };
224    }
225
226    #[test]
227    fn test_money_default() {
228        let default_money = Money::default();
229        let nil_money = Money::none();
230
231        assert_eq!(default_money, nil_money);
232    }
233
234    gen_mul_int_tests! {
235        i64,
236        test_mul_success_i64,
237        test_mul_success_reversed_i64,
238        test_mul_fail_overflow_max_i64,
239        test_mul_fail_overflow_min_i64
240    }
241
242    gen_mul_int_tests! {
243        i32,
244        test_mul_success_i32,
245        test_mul_success_reversed_i32,
246        test_mul_fail_overflow_max_i32,
247        test_mul_fail_overflow_min_i32
248    }
249
250    gen_mul_int_tests! {
251        i16,
252        test_mul_success_i16,
253        test_mul_success_reversed_i16,
254        test_mul_fail_overflow_max_i16,
255        test_mul_fail_overflow_min_i16
256    }
257
258    gen_mul_int_tests! {
259        i8,
260        test_mul_success_i8,
261        test_mul_success_reversed_i8,
262        test_mul_fail_overflow_max_i8,
263        test_mul_fail_overflow_min_i8
264    }
265
266    gen_mul_int_tests! {
267        u32,
268        test_mul_success_u32,
269        test_mul_success_reversed_u32,
270        test_mul_fail_overflow_max_u32,
271        test_mul_fail_overflow_min_u32
272    }
273
274    gen_mul_int_tests! {
275        u16,
276        test_mul_success_u16,
277        test_mul_success_reversed_u16,
278        test_mul_fail_overflow_max_u16,
279        test_mul_fail_overflow_min_u16
280    }
281
282    gen_mul_int_tests! {
283        u8,
284        test_mul_success_u8,
285        test_mul_success_reversed_u8,
286        test_mul_fail_overflow_max_u8,
287        test_mul_fail_overflow_min_u8
288    }
289
290    gen_div_int_tests! {
291        i64,
292        test_div_success_i64,
293        test_div_truncates_i64
294    }
295
296    gen_div_int_tests! {
297        i32,
298        test_div_success_i32,
299        test_div_truncates_i32
300    }
301
302    gen_div_int_tests! {
303        i16,
304        test_div_success_i16,
305        test_div_truncates_i16
306    }
307
308    gen_div_int_tests! {
309        i8,
310        test_div_success_i8,
311        test_div_truncates_i8
312    }
313
314    gen_div_int_tests! {
315        u32,
316        test_div_success_u32,
317        test_div_truncates_u32
318    }
319
320    gen_div_int_tests! {
321        u16,
322        test_div_success_u16,
323        test_div_truncates_u16
324    }
325
326    gen_div_int_tests! {
327        u8,
328        test_div_success_u8,
329        test_div_truncates_u8
330    }
331
332    #[test]
333    fn test_addition_success() {
334        assert_eq!(Money(1) + Money(1), Money(2))
335    }
336
337    #[test]
338    #[should_panic]
339    #[allow(unused_must_use)]
340    fn test_addition_failure_overflow_max() {
341        Money::max() + Money(1);
342    }
343
344    #[test]
345    #[should_panic]
346    #[allow(unused_must_use)]
347    fn test_addition_failure_overflow_min() {
348        Money::min() + Money(-1);
349    }
350
351    #[test]
352    fn test_subtraction_success() {
353        assert_eq!(Money(2) - Money(1), Money(1))
354    }
355
356    #[test]
357    #[should_panic]
358    #[allow(unused_must_use)]
359    fn test_subtraction_failure_overflow_max() {
360        Money::max() - Money(-1);
361    }
362
363    #[test]
364    #[should_panic]
365    #[allow(unused_must_use)]
366    fn test_subtraction_failure_overflow_min() {
367        Money::min() - Money(1);
368    }
369
370    #[test]
371    fn test_money_multiply_f64() {
372        assert_eq!(Money(12300) * 2_f64, Money(24600))
373    }
374
375    #[test]
376    fn test_f64_multiply_money() {
377        assert_eq!(2_f64 * Money(12300), Money(24600))
378    }
379
380    #[test]
381    fn test_money_div_f64() {
382        assert_eq!(Money(12300) / 2_f64, Money(6150))
383    }
384
385    #[test]
386    fn test_money_multiply_f32() {
387        assert_eq!(Money(12300) * 2_f32, Money(24600))
388    }
389
390    #[test]
391    fn test_f32_multiply_money() {
392        assert_eq!(2_f32 * Money(12300), Money(24600))
393    }
394
395    #[test]
396    fn test_money_div_f32() {
397        assert_eq!(Money(12300) / 2_f32, Money(6150))
398    }
399
400    // Comparisons
401    #[test]
402    fn test_eq() {
403        assert_eq!(Money(12300), Money(12300))
404    }
405
406    #[test]
407    fn test_not_eq() {
408        assert_ne!(Money(12300), Money(12400))
409    }
410
411    #[test]
412    fn test_lt_eq() {
413        assert!(Money(12300) <= Money(12300))
414    }
415
416    #[test]
417    fn test_gt_eq() {
418        assert!(Money(12300) >= Money(12300))
419    }
420
421    #[test]
422    fn test_lt() {
423        assert!(Money(12300) < Money(12400))
424    }
425
426    #[test]
427    fn test_gt() {
428        assert!(Money(12300) > Money(12200))
429    }
430
431    #[test]
432    fn test_eq_inverse() {
433        assert!(Money(12300) != Money(12301))
434    }
435
436    #[test]
437    fn test_not_eq_inverse() {
438        assert!(Money(12300) == Money(12300))
439    }
440
441    #[test]
442    fn test_lt_eq_inverse() {
443        assert!(Money(12300) > Money(12299))
444    }
445
446    #[test]
447    fn test_gt_eq_inverse() {
448        assert!(Money(12300) < Money(12301))
449    }
450
451    #[test]
452    fn test_lt_inverse() {
453        assert!(Money(12300) >= Money(12200))
454    }
455
456    #[test]
457    fn test_gt_inverse() {
458        assert!(Money(12300) <= Money(12400))
459    }
460
461    // Rounding vs. Truncation in Division
462    #[test]
463    fn test_rounded_division_f64() {
464        assert_eq!(Money(87808) / 11.0_f64, Money(7983))
465    }
466
467    #[test]
468    fn test_rounded_division_f32() {
469        assert_eq!(Money(87808) / 11.0_f32, Money(7983))
470    }
471
472    #[test]
473    fn test_truncated_division_i64() {
474        assert_eq!(Money(87808) / 11_i64, Money(7982))
475    }
476
477    #[test]
478    fn test_truncated_division_i32() {
479        assert_eq!(Money(87808) / 11_i32, Money(7982))
480    }
481
482    #[test]
483    fn test_truncated_division_i16() {
484        assert_eq!(Money(87808) / 11_i16, Money(7982))
485    }
486
487    #[test]
488    fn test_truncated_division_i8() {
489        assert_eq!(Money(87808) / 11_i8, Money(7982))
490    }
491
492    // Precision loss
493    #[test]
494    fn test_precision_loss_i64() {
495        assert_eq!(
496            Money(9000000000000009900) / 10_i64,
497            Money(900000000000000990)
498        )
499    }
500
501    #[test]
502    fn test_precision_loss_i32() {
503        assert_eq!(
504            Money(9000000000000009900) / 10_i32,
505            Money(900000000000000990)
506        )
507    }
508
509    #[test]
510    fn test_precision_loss_i16() {
511        assert_eq!(
512            Money(9000000000000009900) / 10_i16,
513            Money(900000000000000990)
514        )
515    }
516
517    #[test]
518    fn test_precision_loss_i8() {
519        assert_eq!(
520            Money(9000000000000009900) / 10_i8,
521            Money(900000000000000990)
522        )
523    }
524}