1#[derive(Debug, Clone, PartialEq, Eq)]
37#[non_exhaustive]
38pub enum BigNumberError {
39 InvalidFormat(String),
41 ExponentOutOfRange(String),
43}
44
45impl std::fmt::Display for BigNumberError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 BigNumberError::InvalidFormat(s) => write!(f, "invalid number format: {s}"),
49 BigNumberError::ExponentOutOfRange(s) => {
50 write!(f, "number exponent is outside the supported range: {s}")
51 }
52 }
53 }
54}
55
56impl std::error::Error for BigNumberError {}
57
58fn is_ascii_digits(s: &str) -> bool {
60 !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
61}
62
63fn is_valid_big_integer(s: &str) -> bool {
71 is_ascii_digits(s.strip_prefix('-').unwrap_or(s))
72}
73
74fn validate_big_decimal(s: &str) -> Result<(), BigNumberError> {
85 let invalid_format = || BigNumberError::InvalidFormat(s.to_string());
86 let exponent_out_of_range = || BigNumberError::ExponentOutOfRange(s.to_string());
87
88 let (coefficient, exponent) = match s.split_once(['e', 'E']) {
89 Some((coefficient, exponent)) => match exponent.parse::<i128>() {
90 Ok(exponent) => (coefficient, exponent),
91 Err(error)
92 if matches!(
93 error.kind(),
94 std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow
95 ) =>
96 {
97 return Err(exponent_out_of_range());
98 }
99 Err(_) => return Err(invalid_format()),
100 },
101 None => (s, 0),
102 };
103
104 let coefficient = coefficient.strip_prefix(['-', '+']).unwrap_or(coefficient);
105 let (integer, fraction) = coefficient.split_once('.').unwrap_or((coefficient, ""));
106
107 if (integer.is_empty() && fraction.is_empty())
108 || !integer
109 .bytes()
110 .chain(fraction.bytes())
111 .all(|byte| byte.is_ascii_digit())
112 {
113 return Err(invalid_format());
114 }
115
116 match (fraction.len() as i128).checked_sub(exponent) {
117 Some(scale) if (-(i64::MAX as i128)..=i64::MAX as i128).contains(&scale) => Ok(()),
118 _ => Err(exponent_out_of_range()),
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129#[cfg_attr(
130 all(aws_sdk_unstable, feature = "serde-serialize"),
131 derive(serde::Serialize)
132)]
133pub struct BigInteger(String);
134
135#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
136impl<'de> serde::Deserialize<'de> for BigInteger {
137 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
138 where
139 D: serde::Deserializer<'de>,
140 {
141 let value = <String as serde::Deserialize>::deserialize(deserializer)?;
142 value.parse().map_err(serde::de::Error::custom)
143 }
144}
145
146impl Default for BigInteger {
147 fn default() -> Self {
148 Self("0".to_string())
149 }
150}
151
152impl std::str::FromStr for BigInteger {
153 type Err = BigNumberError;
154
155 fn from_str(s: &str) -> Result<Self, Self::Err> {
156 if !is_valid_big_integer(s) {
157 return Err(BigNumberError::InvalidFormat(s.to_string()));
158 }
159 Ok(Self(s.to_string()))
160 }
161}
162
163impl AsRef<str> for BigInteger {
164 fn as_ref(&self) -> &str {
165 &self.0
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Hash)]
176#[cfg_attr(
177 all(aws_sdk_unstable, feature = "serde-serialize"),
178 derive(serde::Serialize)
179)]
180pub struct BigDecimal(String);
181
182#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
183impl<'de> serde::Deserialize<'de> for BigDecimal {
184 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185 where
186 D: serde::Deserializer<'de>,
187 {
188 let value = <String as serde::Deserialize>::deserialize(deserializer)?;
189 value.parse().map_err(serde::de::Error::custom)
190 }
191}
192
193impl Default for BigDecimal {
194 fn default() -> Self {
195 Self("0.0".to_string())
196 }
197}
198
199impl std::str::FromStr for BigDecimal {
200 type Err = BigNumberError;
201
202 fn from_str(s: &str) -> Result<Self, Self::Err> {
203 validate_big_decimal(s)?;
204 Ok(Self(s.to_string()))
205 }
206}
207
208impl AsRef<str> for BigDecimal {
209 fn as_ref(&self) -> &str {
210 &self.0
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use std::str::FromStr;
218
219 #[test]
220 fn big_integer_basic() {
221 let bi = BigInteger::from_str("12345678901234567890").unwrap();
222 assert_eq!(bi.as_ref(), "12345678901234567890");
223 }
224
225 #[test]
226 fn big_integer_default() {
227 let bi = BigInteger::default();
228 assert_eq!(bi.as_ref(), "0");
229 assert_eq!(BigInteger::from_str(bi.as_ref()).unwrap(), bi);
231 }
232
233 #[test]
234 fn big_decimal_basic() {
235 let bd = BigDecimal::from_str("123.456789").unwrap();
236 assert_eq!(bd.as_ref(), "123.456789");
237 }
238
239 #[test]
240 fn big_decimal_default() {
241 let bd = BigDecimal::default();
242 assert_eq!(bd.as_ref(), "0.0");
243 assert_eq!(BigDecimal::from_str(bd.as_ref()).unwrap(), bd);
244 }
245
246 #[test]
247 fn big_integer_negative() {
248 let bi = BigInteger::from_str("-12345").unwrap();
249 assert_eq!(bi.as_ref(), "-12345");
250 }
251
252 #[test]
253 fn big_decimal_scientific() {
254 let bd = BigDecimal::from_str("1.23e10").unwrap();
255 assert_eq!(bd.as_ref(), "1.23e10");
256
257 let bd = BigDecimal::from_str("1.23E-10").unwrap();
258 assert_eq!(bd.as_ref(), "1.23E-10");
259 }
260
261 #[test]
262 fn big_decimal_accepts_supported_formats() {
263 for value in ["0", "+5", "-0.0", ".5", "-.5", "5.", "1.5E+3"] {
264 assert!(BigDecimal::from_str(value).is_ok(), "{value}");
265 }
266 }
267
268 #[test]
269 fn big_decimal_rejects_malformed_values() {
270 for value in [
271 "1.2.3", "-", "+", ".", "e", "E", "1e", "1e+", "--5", "1-2", "12-34", "..", "1.2e3.4",
272 "+-1",
273 ] {
274 assert_eq!(
275 BigDecimal::from_str(value),
276 Err(BigNumberError::InvalidFormat(value.to_string())),
277 "{value}"
278 );
279 }
280 }
281
282 #[test]
283 fn big_decimal_rejects_out_of_range_exponents() {
284 for value in [
285 "1E99999999999999999999",
286 "1E-99999999999999999999",
287 "1e9223372036854775808",
288 "1e-9223372036854775808",
289 "1e999999999999999999999999999999999999999",
290 ] {
291 assert_eq!(
292 BigDecimal::from_str(value),
293 Err(BigNumberError::ExponentOutOfRange(value.to_string())),
294 "{value}"
295 );
296 }
297 }
298
299 #[test]
300 fn big_integer_rejects_json_injection() {
301 assert!(BigInteger::from_str("123, \"injected\": true").is_err());
303 assert!(BigInteger::from_str("123}").is_err());
304 assert!(BigInteger::from_str("{\"hacked\": 1}").is_err());
305 assert!(BigInteger::from_str("123\"").is_err());
306 assert!(BigInteger::from_str("123\\n456").is_err());
307 }
308
309 #[test]
310 fn big_decimal_rejects_json_injection() {
311 assert!(BigDecimal::from_str("123.45, \"injected\": true").is_err());
312 assert!(BigDecimal::from_str("123.45}").is_err());
313 assert!(BigDecimal::from_str("{\"hacked\": 1.0}").is_err());
314 }
315
316 #[test]
317 fn big_integer_rejects_invalid_chars() {
318 assert!(BigInteger::from_str("abc").is_err());
319 assert!(BigInteger::from_str("123abc").is_err());
320 assert!(BigInteger::from_str("12 34").is_err());
321 assert!(BigInteger::from_str("").is_err());
322 }
323
324 #[test]
325 fn big_integer_rejects_decimal_and_scientific() {
326 assert!(BigInteger::from_str("123.45").is_err());
328 assert!(BigInteger::from_str("123.0").is_err());
329
330 assert!(BigInteger::from_str("1e10").is_err());
332 assert!(BigInteger::from_str("1E10").is_err());
333 assert!(BigInteger::from_str("1.23e10").is_err());
334 }
335
336 #[test]
337 fn big_decimal_rejects_invalid_chars() {
338 assert!(BigDecimal::from_str("abc").is_err());
339 assert!(BigDecimal::from_str("123.45abc").is_err());
340 assert!(BigDecimal::from_str("12.34 56").is_err());
341 assert!(BigDecimal::from_str("").is_err());
342 }
343
344 #[test]
347 fn big_integer_rejects_a_leading_plus() {
348 assert!(BigInteger::from_str("+123").is_err());
351 assert!(BigInteger::from_str("+0").is_err());
352 }
353
354 #[test]
355 fn big_numbers_reject_bare_and_repeated_signs() {
356 for bad in ["-", "+", "--5", "++5", "-+5", "+-5", "5-", "5+"] {
357 assert!(
358 BigInteger::from_str(bad).is_err(),
359 "BigInteger::from_str({bad:?}) should fail"
360 );
361 assert!(
362 BigDecimal::from_str(bad).is_err(),
363 "BigDecimal::from_str({bad:?}) should fail"
364 );
365 }
366 }
367
368 #[test]
369 fn big_decimal_rejects_malformed_separators_and_exponents() {
370 for bad in [
371 "1..2", "1.2.3", "0.0.0", "1e", "1E", "1e+", "1e-", "e10", "-e10", "1e2e3", "1.5e", "1e2.5", "1e 2", "1 .5", ".", "1.2e3e", ] {
388 assert!(
389 BigDecimal::from_str(bad).is_err(),
390 "BigDecimal::from_str({bad:?}) should fail"
391 );
392 }
393 }
394
395 #[test]
396 fn big_decimal_accepts_additional_supported_formats() {
397 for good in [
398 "0",
399 "-0",
400 "123",
401 "-123",
402 "1.5",
403 "-0.5",
404 "1e3",
405 "1E3",
406 "1e+9",
407 "1e-9",
408 "1.23e10",
409 "1.23E-10",
410 "10.0",
411 "5e-3",
412 "0.123456789012345678901234567890",
413 "12345678901234567890.123",
414 "1.234e500",
415 ] {
416 let bd = BigDecimal::from_str(good)
417 .unwrap_or_else(|e| panic!("BigDecimal::from_str({good:?}) should succeed: {e}"));
418 assert_eq!(bd.as_ref(), good);
420 }
421 }
422
423 #[test]
424 fn big_numbers_still_accept_leading_zeros() {
425 for good in ["00123", "007", "-01", "0000", "-0000"] {
430 let bi = BigInteger::from_str(good)
431 .unwrap_or_else(|e| panic!("BigInteger::from_str({good:?}) should succeed: {e}"));
432 assert_eq!(bi.as_ref(), good);
433 let bd = BigDecimal::from_str(good)
434 .unwrap_or_else(|e| panic!("BigDecimal::from_str({good:?}) should succeed: {e}"));
435 assert_eq!(bd.as_ref(), good);
436 }
437 for good in ["00.1", "-01.5", "007e2"] {
438 let bd = BigDecimal::from_str(good)
439 .unwrap_or_else(|e| panic!("BigDecimal::from_str({good:?}) should succeed: {e}"));
440 assert_eq!(bd.as_ref(), good);
441 }
442 }
443
444 #[test]
445 fn big_integer_accepts_only_digits_after_an_optional_minus() {
446 for good in ["0", "-0", "1", "-1", "12345678901234567890"] {
447 assert!(
448 BigInteger::from_str(good).is_ok(),
449 "BigInteger::from_str({good:?}) should succeed"
450 );
451 }
452 for bad in ["1_000", "1,000", "0x1f", " 1", "1 ", "\t1", "1\n"] {
453 assert!(
454 BigInteger::from_str(bad).is_err(),
455 "BigInteger::from_str({bad:?}) should fail"
456 );
457 }
458 }
459
460 #[test]
461 fn big_number_error_reports_the_offending_input() {
462 let err = BigInteger::from_str("+123").unwrap_err();
463 assert_eq!(err, BigNumberError::InvalidFormat("+123".to_string()));
464 assert!(err.to_string().contains("+123"));
465 }
466
467 #[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
468 #[test]
469 fn serde_deserialization_preserves_big_number_invariants() {
470 let integer: BigInteger = serde_json::from_str(r#""00123""#).unwrap();
471 assert_eq!(integer.as_ref(), "00123");
472 assert!(serde_json::from_str::<BigInteger>(r#""+123""#).is_err());
473
474 for value in ["+5", ".5", "-.5", "5.", "1.5E+3"] {
475 let json = format!(r#""{value}""#);
476 let decimal: BigDecimal = serde_json::from_str(&json).unwrap();
477 assert_eq!(decimal.as_ref(), value);
478 }
479
480 for value in ["1.2.3", "--5", "1e"] {
481 let json = format!(r#""{value}""#);
482 assert!(serde_json::from_str::<BigDecimal>(&json).is_err());
483 }
484
485 let error = serde_json::from_str::<BigDecimal>(r#""1E99999999999999999999""#).unwrap_err();
486 assert!(error.to_string().contains("outside the supported range"));
487 }
488}