1use crate::errors::{Error, Result};
11use serde::{Deserialize, Serialize};
12use std::{
13 fmt::{self, Debug, Display, Formatter},
14 str::FromStr,
15};
16
17const MONEY_TO_RAW_POWER_OF_10_CONVERSION: u32 = 9;
19
20const MONEY_TO_RAW_CONVERSION: u64 = 1_000_000_000;
22
23#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
24pub struct Money(u64);
26
27impl Money {
28 pub const fn zero() -> Self {
30 Self(0)
31 }
32
33 pub const fn from_nano(value: u64) -> Self {
35 Self(value)
36 }
37
38 pub const fn as_nano(self) -> u64 {
40 self.0
41 }
42
43 pub fn checked_add(self, rhs: Money) -> Option<Money> {
45 self.0.checked_add(rhs.0).map(Self::from_nano)
46 }
47
48 pub fn checked_sub(self, rhs: Money) -> Option<Money> {
50 self.0.checked_sub(rhs.0).map(Self::from_nano)
51 }
52}
53
54impl FromStr for Money {
55 type Err = Error;
56
57 fn from_str(value_str: &str) -> Result<Self> {
58 let mut itr = value_str.splitn(2, '.');
59 let converted_units = {
60 let units = itr
61 .next()
62 .and_then(|s| s.parse::<u64>().ok())
63 .ok_or_else(|| Error::FailedToParse("Can't parse Money units".to_string()))?;
64
65 units
66 .checked_mul(MONEY_TO_RAW_CONVERSION)
67 .ok_or_else(|| Error::ExcessiveValue)?
68 };
69
70 let remainder = {
71 let remainder_str = itr.next().unwrap_or_default().trim_end_matches('0');
72
73 if remainder_str.is_empty() {
74 0
75 } else {
76 let parsed_remainder = remainder_str
77 .parse::<u64>()
78 .map_err(|_| Error::FailedToParse("Can't parse Money remainder".to_string()))?;
79
80 let remainder_conversion = MONEY_TO_RAW_POWER_OF_10_CONVERSION
81 .checked_sub(remainder_str.len() as u32)
82 .ok_or_else(|| Error::LossOfPrecision)?;
83 parsed_remainder * 10_u64.pow(remainder_conversion)
84 }
85 };
86
87 Ok(Self::from_nano(converted_units + remainder))
88 }
89}
90
91impl Debug for Money {
92 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
93 Display::fmt(self, formatter)
94 }
95}
96
97impl Display for Money {
98 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
99 let unit = self.0 / MONEY_TO_RAW_CONVERSION;
100 let remainder = self.0 % MONEY_TO_RAW_CONVERSION;
101 write!(formatter, "{}.{}", unit, format!("{:09}", remainder))
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use std::u64;
109 use unwrap::unwrap;
110
111 #[test]
112 fn from_str() {
113 assert_eq!(Money(0), unwrap!(Money::from_str("0")));
114 assert_eq!(Money(0), unwrap!(Money::from_str("0.")));
115 assert_eq!(Money(0), unwrap!(Money::from_str("0.0")));
116 assert_eq!(Money(1), unwrap!(Money::from_str("0.000000001")));
117 assert_eq!(Money(1_000_000_000), unwrap!(Money::from_str("1")));
118 assert_eq!(Money(1_000_000_000), unwrap!(Money::from_str("1.")));
119 assert_eq!(Money(1_000_000_000), unwrap!(Money::from_str("1.0")));
120 assert_eq!(
121 Money(1_000_000_001),
122 unwrap!(Money::from_str("1.000000001"))
123 );
124 assert_eq!(Money(1_100_000_000), unwrap!(Money::from_str("1.1")));
125 assert_eq!(
126 Money(1_100_000_001),
127 unwrap!(Money::from_str("1.100000001"))
128 );
129 assert_eq!(
130 Money(4_294_967_295_000_000_000),
131 unwrap!(Money::from_str("4294967295"))
132 );
133 assert_eq!(
134 Money(4_294_967_295_999_999_999),
135 unwrap!(Money::from_str("4294967295.999999999")),
136 );
137 assert_eq!(
138 Money(4_294_967_295_999_999_999),
139 unwrap!(Money::from_str("4294967295.9999999990000")),
140 );
141
142 assert_eq!(
143 Err(Error::FailedToParse("Can't parse Money units".to_string())),
144 Money::from_str("a")
145 );
146 assert_eq!(
147 Err(Error::FailedToParse(
148 "Can't parse Money remainder".to_string()
149 )),
150 Money::from_str("0.a")
151 );
152 assert_eq!(
153 Err(Error::FailedToParse(
154 "Can't parse Money remainder".to_string()
155 )),
156 Money::from_str("0.0.0")
157 );
158 assert_eq!(Err(Error::LossOfPrecision), Money::from_str("0.0000000009"));
159 assert_eq!(Err(Error::ExcessiveValue), Money::from_str("18446744074"));
160 }
161
162 #[test]
163 fn display() {
164 assert_eq!("0.000000000", format!("{}", Money(0)));
165 assert_eq!("0.000000001", format!("{}", Money(1)));
166 assert_eq!("0.000000010", format!("{}", Money(10)));
167 assert_eq!("1.000000000", format!("{}", Money(1_000_000_000)));
168 assert_eq!("1.000000001", format!("{}", Money(1_000_000_001)));
169 assert_eq!(
170 "4294967295.000000000",
171 format!("{}", Money(4_294_967_295_000_000_000))
172 );
173 }
174
175 #[test]
176 fn checked_add_sub() {
177 assert_eq!(Some(Money(3)), Money(1).checked_add(Money(2)));
178 assert_eq!(None, Money(u64::MAX).checked_add(Money(1)));
179 assert_eq!(None, Money(u64::MAX).checked_add(Money(u64::MAX)));
180
181 assert_eq!(Some(Money(0)), Money(u64::MAX).checked_sub(Money(u64::MAX)));
182 assert_eq!(None, Money(0).checked_sub(Money(u64::MAX)));
183 assert_eq!(None, Money(10).checked_sub(Money(11)));
184 }
185}