aws_smithy_types/
big_number.rs1#[derive(Debug, Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum BigNumberError {
15 InvalidFormat(String),
17 ExponentOutOfRange(String),
19}
20
21impl std::fmt::Display for BigNumberError {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 BigNumberError::InvalidFormat(s) => write!(f, "invalid number format: {s}"),
25 BigNumberError::ExponentOutOfRange(s) => {
26 write!(f, "number exponent is outside the supported range: {s}")
27 }
28 }
29 }
30}
31
32impl std::error::Error for BigNumberError {}
33
34fn is_valid_big_integer(s: &str) -> bool {
37 if s.is_empty() {
38 return false;
39 }
40
41 let mut chars = s.chars();
42
43 match chars.next() {
45 Some('-') | Some('+') | Some('0'..='9') => {}
46 _ => return false,
47 }
48
49 chars.all(|c| c.is_ascii_digit())
51}
52
53fn validate_big_decimal(s: &str) -> Result<(), BigNumberError> {
56 let invalid_format = || BigNumberError::InvalidFormat(s.to_string());
57 let exponent_out_of_range = || BigNumberError::ExponentOutOfRange(s.to_string());
58
59 let (coefficient, exponent) = match s.split_once(['e', 'E']) {
60 Some((coefficient, exponent)) => match exponent.parse::<i128>() {
61 Ok(exponent) => (coefficient, exponent),
62 Err(error)
63 if matches!(
64 error.kind(),
65 std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow
66 ) =>
67 {
68 return Err(exponent_out_of_range());
69 }
70 Err(_) => return Err(invalid_format()),
71 },
72 None => (s, 0),
73 };
74
75 let coefficient = coefficient.strip_prefix(['-', '+']).unwrap_or(coefficient);
76 let (integer, fraction) = coefficient.split_once('.').unwrap_or((coefficient, ""));
77
78 if (integer.is_empty() && fraction.is_empty())
79 || !integer
80 .bytes()
81 .chain(fraction.bytes())
82 .all(|byte| byte.is_ascii_digit())
83 {
84 return Err(invalid_format());
85 }
86
87 match (fraction.len() as i128).checked_sub(exponent) {
88 Some(scale) if (-(i64::MAX as i128)..=i64::MAX as i128).contains(&scale) => Ok(()),
89 _ => Err(exponent_out_of_range()),
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98pub struct BigInteger(String);
99
100impl Default for BigInteger {
101 fn default() -> Self {
102 Self("0".to_string())
103 }
104}
105
106impl std::str::FromStr for BigInteger {
107 type Err = BigNumberError;
108
109 fn from_str(s: &str) -> Result<Self, Self::Err> {
110 if !is_valid_big_integer(s) {
111 return Err(BigNumberError::InvalidFormat(s.to_string()));
112 }
113 Ok(Self(s.to_string()))
114 }
115}
116
117impl AsRef<str> for BigInteger {
118 fn as_ref(&self) -> &str {
119 &self.0
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Hash)]
128pub struct BigDecimal(String);
129
130impl Default for BigDecimal {
131 fn default() -> Self {
132 Self("0.0".to_string())
133 }
134}
135
136impl std::str::FromStr for BigDecimal {
137 type Err = BigNumberError;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 validate_big_decimal(s)?;
141 Ok(Self(s.to_string()))
142 }
143}
144
145impl AsRef<str> for BigDecimal {
146 fn as_ref(&self) -> &str {
147 &self.0
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::str::FromStr;
155
156 #[test]
157 fn big_integer_basic() {
158 let bi = BigInteger::from_str("12345678901234567890").unwrap();
159 assert_eq!(bi.as_ref(), "12345678901234567890");
160 }
161
162 #[test]
163 fn big_integer_default() {
164 let bi = BigInteger::default();
165 assert_eq!(bi.as_ref(), "0");
166 }
167
168 #[test]
169 fn big_decimal_basic() {
170 let bd = BigDecimal::from_str("123.456789").unwrap();
171 assert_eq!(bd.as_ref(), "123.456789");
172 }
173
174 #[test]
175 fn big_decimal_default() {
176 let bd = BigDecimal::default();
177 assert_eq!(bd.as_ref(), "0.0");
178 }
179
180 #[test]
181 fn big_integer_negative() {
182 let bi = BigInteger::from_str("-12345").unwrap();
183 assert_eq!(bi.as_ref(), "-12345");
184 }
185
186 #[test]
187 fn big_decimal_scientific() {
188 let bd = BigDecimal::from_str("1.23e10").unwrap();
189 assert_eq!(bd.as_ref(), "1.23e10");
190
191 let bd = BigDecimal::from_str("1.23E-10").unwrap();
192 assert_eq!(bd.as_ref(), "1.23E-10");
193 }
194
195 #[test]
196 fn big_decimal_accepts_supported_formats() {
197 for value in ["0", "+5", "-0.0", ".5", "-.5", "5.", "1.5E+3"] {
198 assert!(BigDecimal::from_str(value).is_ok(), "{value}");
199 }
200 }
201
202 #[test]
203 fn big_decimal_rejects_malformed_values() {
204 for value in [
205 "1.2.3", "-", "+", ".", "e", "E", "1e", "1e+", "--5", "1-2", "12-34", "..", "1.2e3.4",
206 "+-1",
207 ] {
208 assert_eq!(
209 BigDecimal::from_str(value),
210 Err(BigNumberError::InvalidFormat(value.to_string())),
211 "{value}"
212 );
213 }
214 }
215
216 #[test]
217 fn big_decimal_rejects_out_of_range_exponents() {
218 for value in [
219 "1E99999999999999999999",
220 "1E-99999999999999999999",
221 "1e9223372036854775808",
222 "1e-9223372036854775808",
223 "1e999999999999999999999999999999999999999",
224 ] {
225 assert_eq!(
226 BigDecimal::from_str(value),
227 Err(BigNumberError::ExponentOutOfRange(value.to_string())),
228 "{value}"
229 );
230 }
231 }
232
233 #[test]
234 fn big_integer_rejects_json_injection() {
235 assert!(BigInteger::from_str("123, \"injected\": true").is_err());
237 assert!(BigInteger::from_str("123}").is_err());
238 assert!(BigInteger::from_str("{\"hacked\": 1}").is_err());
239 assert!(BigInteger::from_str("123\"").is_err());
240 assert!(BigInteger::from_str("123\\n456").is_err());
241 }
242
243 #[test]
244 fn big_decimal_rejects_json_injection() {
245 assert!(BigDecimal::from_str("123.45, \"injected\": true").is_err());
246 assert!(BigDecimal::from_str("123.45}").is_err());
247 assert!(BigDecimal::from_str("{\"hacked\": 1.0}").is_err());
248 }
249
250 #[test]
251 fn big_integer_rejects_invalid_chars() {
252 assert!(BigInteger::from_str("abc").is_err());
253 assert!(BigInteger::from_str("123abc").is_err());
254 assert!(BigInteger::from_str("12 34").is_err());
255 assert!(BigInteger::from_str("").is_err());
256 }
257
258 #[test]
259 fn big_integer_rejects_decimal_and_scientific() {
260 assert!(BigInteger::from_str("123.45").is_err());
262 assert!(BigInteger::from_str("123.0").is_err());
263
264 assert!(BigInteger::from_str("1e10").is_err());
266 assert!(BigInteger::from_str("1E10").is_err());
267 assert!(BigInteger::from_str("1.23e10").is_err());
268 }
269
270 #[test]
271 fn big_integer_accepts_signs() {
272 assert!(BigInteger::from_str("+123").is_ok());
273 assert!(BigInteger::from_str("-123").is_ok());
274 assert_eq!(BigInteger::from_str("+123").unwrap().as_ref(), "+123");
275 }
276
277 #[test]
278 fn big_decimal_rejects_invalid_chars() {
279 assert!(BigDecimal::from_str("abc").is_err());
280 assert!(BigDecimal::from_str("123.45abc").is_err());
281 assert!(BigDecimal::from_str("12.34 56").is_err());
282 assert!(BigDecimal::from_str("").is_err());
283 }
284}