Skip to main content

aws_smithy_types/
big_number.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Big number types represented as strings.
7//!
8//! These types are simple string wrappers that allow users to parse and format
9//! big numbers using their preferred library.
10
11/// Error type for BigInteger and BigDecimal parsing.
12#[derive(Debug, Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum BigNumberError {
15    /// The input string is not a valid number format.
16    InvalidFormat(String),
17}
18
19impl std::fmt::Display for BigNumberError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            BigNumberError::InvalidFormat(s) => write!(f, "invalid number format: {s}"),
23        }
24    }
25}
26
27impl std::error::Error for BigNumberError {}
28
29/// Validates that a string is a valid BigInteger format.
30/// Only allows digits and an optional leading sign.
31fn is_valid_big_integer(s: &str) -> bool {
32    if s.is_empty() {
33        return false;
34    }
35
36    let mut chars = s.chars();
37
38    // Check first character (can be sign or digit)
39    match chars.next() {
40        Some('-') | Some('+') | Some('0'..='9') => {}
41        _ => return false,
42    }
43
44    // Rest must be digits only
45    chars.all(|c| c.is_ascii_digit())
46}
47
48/// Validates that a string is a valid BigDecimal format.
49/// Allows digits, sign, decimal point, and scientific notation.
50fn is_valid_big_decimal(s: &str) -> bool {
51    if s.is_empty() {
52        return false;
53    }
54
55    s.chars()
56        .all(|c| matches!(c, '0'..='9' | '-' | '+' | '.' | 'e' | 'E'))
57}
58
59/// A BigInteger represented as a string.
60///
61/// This type does not perform arithmetic operations. Users should parse the string
62/// with their preferred big integer library.
63#[derive(Debug, Clone, PartialEq, Eq, Hash)]
64pub struct BigInteger(String);
65
66impl Default for BigInteger {
67    fn default() -> Self {
68        Self("0".to_string())
69    }
70}
71
72impl std::str::FromStr for BigInteger {
73    type Err = BigNumberError;
74
75    fn from_str(s: &str) -> Result<Self, Self::Err> {
76        if !is_valid_big_integer(s) {
77            return Err(BigNumberError::InvalidFormat(s.to_string()));
78        }
79        Ok(Self(s.to_string()))
80    }
81}
82
83impl AsRef<str> for BigInteger {
84    fn as_ref(&self) -> &str {
85        &self.0
86    }
87}
88
89/// A big decimal represented as a string.
90///
91/// This type does not perform arithmetic operations. Users should parse the string
92/// with their preferred big decimal library.
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
94pub struct BigDecimal(String);
95
96impl Default for BigDecimal {
97    fn default() -> Self {
98        Self("0.0".to_string())
99    }
100}
101
102impl std::str::FromStr for BigDecimal {
103    type Err = BigNumberError;
104
105    fn from_str(s: &str) -> Result<Self, Self::Err> {
106        if !is_valid_big_decimal(s) {
107            return Err(BigNumberError::InvalidFormat(s.to_string()));
108        }
109        Ok(Self(s.to_string()))
110    }
111}
112
113impl AsRef<str> for BigDecimal {
114    fn as_ref(&self) -> &str {
115        &self.0
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use std::str::FromStr;
123
124    #[test]
125    fn big_integer_basic() {
126        let bi = BigInteger::from_str("12345678901234567890").unwrap();
127        assert_eq!(bi.as_ref(), "12345678901234567890");
128    }
129
130    #[test]
131    fn big_integer_default() {
132        let bi = BigInteger::default();
133        assert_eq!(bi.as_ref(), "0");
134    }
135
136    #[test]
137    fn big_decimal_basic() {
138        let bd = BigDecimal::from_str("123.456789").unwrap();
139        assert_eq!(bd.as_ref(), "123.456789");
140    }
141
142    #[test]
143    fn big_decimal_default() {
144        let bd = BigDecimal::default();
145        assert_eq!(bd.as_ref(), "0.0");
146    }
147
148    #[test]
149    fn big_integer_negative() {
150        let bi = BigInteger::from_str("-12345").unwrap();
151        assert_eq!(bi.as_ref(), "-12345");
152    }
153
154    #[test]
155    fn big_decimal_scientific() {
156        let bd = BigDecimal::from_str("1.23e10").unwrap();
157        assert_eq!(bd.as_ref(), "1.23e10");
158
159        let bd = BigDecimal::from_str("1.23E-10").unwrap();
160        assert_eq!(bd.as_ref(), "1.23E-10");
161    }
162
163    #[test]
164    fn big_integer_rejects_json_injection() {
165        // Reject strings with JSON special characters
166        assert!(BigInteger::from_str("123, \"injected\": true").is_err());
167        assert!(BigInteger::from_str("123}").is_err());
168        assert!(BigInteger::from_str("{\"hacked\": 1}").is_err());
169        assert!(BigInteger::from_str("123\"").is_err());
170        assert!(BigInteger::from_str("123\\n456").is_err());
171    }
172
173    #[test]
174    fn big_decimal_rejects_json_injection() {
175        assert!(BigDecimal::from_str("123.45, \"injected\": true").is_err());
176        assert!(BigDecimal::from_str("123.45}").is_err());
177        assert!(BigDecimal::from_str("{\"hacked\": 1.0}").is_err());
178    }
179
180    #[test]
181    fn big_integer_rejects_invalid_chars() {
182        assert!(BigInteger::from_str("abc").is_err());
183        assert!(BigInteger::from_str("123abc").is_err());
184        assert!(BigInteger::from_str("12 34").is_err());
185        assert!(BigInteger::from_str("").is_err());
186    }
187
188    #[test]
189    fn big_integer_rejects_decimal_and_scientific() {
190        // BigInteger should reject decimal points
191        assert!(BigInteger::from_str("123.45").is_err());
192        assert!(BigInteger::from_str("123.0").is_err());
193
194        // BigInteger should reject scientific notation
195        assert!(BigInteger::from_str("1e10").is_err());
196        assert!(BigInteger::from_str("1E10").is_err());
197        assert!(BigInteger::from_str("1.23e10").is_err());
198    }
199
200    #[test]
201    fn big_integer_accepts_signs() {
202        assert!(BigInteger::from_str("+123").is_ok());
203        assert!(BigInteger::from_str("-123").is_ok());
204        assert_eq!(BigInteger::from_str("+123").unwrap().as_ref(), "+123");
205    }
206
207    #[test]
208    fn big_decimal_rejects_invalid_chars() {
209        assert!(BigDecimal::from_str("abc").is_err());
210        assert!(BigDecimal::from_str("123.45abc").is_err());
211        assert!(BigDecimal::from_str("12.34 56").is_err());
212        assert!(BigDecimal::from_str("").is_err());
213    }
214}