safe_nd/
money.rs

1// Copyright 2019 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under the MIT license <LICENSE-MIT
4// https://opensource.org/licenses/MIT> or the Modified BSD license <LICENSE-BSD
5// https://opensource.org/licenses/BSD-3-Clause>, at your option. This file may not be copied,
6// modified, or distributed except according to those terms. Please review the Licences for the
7// specific language governing permissions and limitations relating to use of the SAFE Network
8// Software.
9
10use crate::errors::{Error, Result};
11use serde::{Deserialize, Serialize};
12use std::{
13    fmt::{self, Debug, Display, Formatter},
14    str::FromStr,
15};
16
17/// The conversion from Money to raw value
18const MONEY_TO_RAW_POWER_OF_10_CONVERSION: u32 = 9;
19
20/// The conversion from Money to raw value
21const MONEY_TO_RAW_CONVERSION: u64 = 1_000_000_000;
22
23#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
24/// Structure representing a safeMoney amount.
25pub struct Money(u64);
26
27impl Money {
28    /// Type safe representation of zero Money.
29    pub const fn zero() -> Self {
30        Self(0)
31    }
32
33    /// New value from a number of nano Money.
34    pub const fn from_nano(value: u64) -> Self {
35        Self(value)
36    }
37
38    /// Total Money expressed in number of nano Money.
39    pub const fn as_nano(self) -> u64 {
40        self.0
41    }
42
43    /// Computes `self + rhs`, returning `None` if overflow occurred.
44    pub fn checked_add(self, rhs: Money) -> Option<Money> {
45        self.0.checked_add(rhs.0).map(Self::from_nano)
46    }
47
48    /// Computes `self - rhs`, returning `None` if overflow occurred.
49    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}