near_token/trait_impls/
from_str.rs

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