near_token/trait_impls/
from_str.rs

1use crate::{NearToken, NearTokenError, ONE_NEAR};
2
3impl std::str::FromStr for NearToken {
4    type Err = NearTokenError;
5    fn from_str(s: &str) -> Result<Self, Self::Err> {
6        let uppercase_s = s.trim().to_ascii_uppercase();
7        let (value, unit) = uppercase_s.split_at(
8            s.find(|c: char| c.is_ascii_alphabetic())
9                .ok_or_else(|| NearTokenError::InvalidTokenUnit(s.to_owned()))?,
10        );
11        let unit_precision = match unit {
12            "YN" | "YNEAR" | "YOCTONEAR" => 1,
13            "NEAR" | "N" => ONE_NEAR,
14            _ => return Err(NearTokenError::InvalidTokenUnit(s.to_owned())),
15        };
16        Ok(NearToken::from_yoctonear(
17            crate::utils::parse_decimal_number(value.trim(), unit_precision)
18                .map_err(NearTokenError::InvalidTokensAmount)?,
19        ))
20    }
21}
22
23#[cfg(test)]
24mod test {
25    use std::str::FromStr;
26
27    use crate::{DecimalNumberParsingError, NearToken, NearTokenError};
28
29    #[test]
30    fn parse_decimal_number() {
31        let data = "0.123456 near";
32        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
33        assert_eq!(
34            gas.unwrap(),
35            NearToken::from_yoctonear(123456000000000000000000)
36        );
37    }
38    #[test]
39    fn parse_number_with_decimal_part() {
40        let data = "11.123456 near";
41        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
42        assert_eq!(
43            gas.unwrap(),
44            NearToken::from_yoctonear(11123456000000000000000000)
45        );
46    }
47
48    #[test]
49    fn parse_yocto_number() {
50        let data = "123456 YN";
51        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
52        assert_eq!(gas.unwrap(), NearToken::from_yoctonear(123456));
53    }
54
55    #[test]
56    fn doubledot() {
57        let data = "1.1.1 Near";
58        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
59        assert_eq!(
60            gas,
61            Err(NearTokenError::InvalidTokensAmount(
62                DecimalNumberParsingError::InvalidNumber("1.1.1".to_owned())
63            ))
64        )
65    }
66
67    #[test]
68    fn space_after_dot() {
69        let data = "1. 0 near";
70        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
71        assert_eq!(
72            gas,
73            Err(NearTokenError::InvalidTokensAmount(
74                DecimalNumberParsingError::InvalidNumber("1. 0".to_owned())
75            ))
76        )
77    }
78
79    #[test]
80    fn incorect_currency() {
81        let data = "0 pas";
82        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
83        assert_eq!(gas, Err(NearTokenError::InvalidTokenUnit(data.to_owned())))
84    }
85
86    #[test]
87    fn without_currency() {
88        let data = "0";
89        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
90        assert_eq!(gas, Err(NearTokenError::InvalidTokenUnit("0".to_owned())))
91    }
92
93    #[test]
94    fn invalid_whole() {
95        let data = "-1 Near";
96        let gas: Result<NearToken, NearTokenError> = FromStr::from_str(data);
97        assert_eq!(
98            gas,
99            Err(NearTokenError::InvalidTokensAmount(
100                DecimalNumberParsingError::InvalidNumber("-1".to_owned())
101            ))
102        )
103    }
104
105    #[test]
106    fn test_from_str_f64_gas_without_int() {
107        let near_gas = NearToken::from_str(".055 ynear").unwrap_err();
108        assert_eq!(
109            near_gas,
110            NearTokenError::InvalidTokensAmount(DecimalNumberParsingError::InvalidNumber(
111                ".055".to_string()
112            ))
113        );
114    }
115
116    #[test]
117    fn test_from_str_without_unit() {
118        let near_gas = NearToken::from_str("100").unwrap_err();
119        assert_eq!(
120            near_gas,
121            NearTokenError::InvalidTokenUnit("100".to_string())
122        );
123    }
124
125    #[test]
126    fn test_from_str_incorrect_unit() {
127        let near_gas = NearToken::from_str("100 UAH").unwrap_err();
128        assert_eq!(
129            near_gas,
130            NearTokenError::InvalidTokenUnit("100 UAH".to_string())
131        );
132    }
133
134    #[test]
135    fn test_from_str_invalid_double_dot() {
136        let near_gas = NearToken::from_str("100.55.").unwrap_err();
137        assert_eq!(
138            near_gas,
139            NearTokenError::InvalidTokenUnit("100.55.".to_string())
140        );
141    }
142
143    #[test]
144    fn test_from_str_large_fractional_part() {
145        let near_gas = NearToken::from_str("100.1111122222333 ynear").unwrap_err(); // 13 digits after "."
146        assert_eq!(
147            near_gas,
148            NearTokenError::InvalidTokensAmount(DecimalNumberParsingError::LongFractional(
149                "1111122222333".to_string()
150            ))
151        );
152    }
153}