Skip to main content

reifydb_type/value/uint/
parse.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::borrow::Cow;
5
6use num_bigint::{BigInt, Sign};
7
8use crate::{
9	error::{Error, TypeError},
10	fragment::Fragment,
11	value::{r#type::Type, uint::Uint},
12};
13
14pub fn parse_uint(fragment: Fragment) -> Result<Uint, Error> {
15	let raw_value = fragment.text();
16
17	let needs_trimming = raw_value.as_bytes().first().is_some_and(|&b| b.is_ascii_whitespace())
18		|| raw_value.as_bytes().last().is_some_and(|&b| b.is_ascii_whitespace());
19
20	let has_underscores = raw_value.as_bytes().contains(&b'_');
21
22	let value = match (needs_trimming, has_underscores) {
23		(false, false) => Cow::Borrowed(raw_value),
24
25		(true, false) => Cow::Borrowed(raw_value.trim()),
26		(false, true) => Cow::Owned(raw_value.replace('_', "")),
27		(true, true) => Cow::Owned(raw_value.trim().replace('_', "")),
28	};
29
30	if value.is_empty() {
31		return Err(TypeError::InvalidNumberFormat {
32			target: Type::Uint,
33			fragment,
34		}
35		.into());
36	}
37
38	if value.starts_with('-')
39		&& value != "-0.0"
40		&& value != "-0"
41		&& let Ok(bigint) = value.parse::<BigInt>()
42		&& bigint.sign() == Sign::Minus
43	{
44		return Err(TypeError::NumberOutOfRange {
45			target: Type::Uint,
46			fragment,
47			descriptor: None,
48		}
49		.into());
50	}
51
52	match value.parse::<BigInt>() {
53		Ok(v) => {
54			if v.sign() == Sign::Minus {
55				return Err(TypeError::NumberOutOfRange {
56					target: Type::Uint,
57					fragment,
58					descriptor: None,
59				}
60				.into());
61			}
62			Ok(Uint::from(v))
63		}
64		Err(_) => {
65			if let Ok(f) = value.parse::<f64>() {
66				if f.is_infinite() {
67					Err(TypeError::NumberOutOfRange {
68						target: Type::Uint,
69						fragment,
70						descriptor: None,
71					}
72					.into())
73				} else {
74					let truncated = f.trunc();
75
76					if truncated < 0.0 && truncated != -0.0 {
77						return Err(TypeError::NumberOutOfRange {
78							target: Type::Uint,
79							fragment,
80							descriptor: None,
81						}
82						.into());
83					}
84
85					let abs_truncated = if truncated == -0.0 {
86						0.0
87					} else {
88						truncated
89					};
90					if let Ok(bigint) = format!("{:.0}", abs_truncated).parse::<BigInt>() {
91						Ok(Uint::from(bigint))
92					} else {
93						Err(TypeError::InvalidNumberFormat {
94							target: Type::Uint,
95							fragment,
96						}
97						.into())
98					}
99				}
100			} else if value.contains('-') {
101				Err(TypeError::NumberOutOfRange {
102					target: Type::Uint,
103					fragment,
104					descriptor: None,
105				}
106				.into())
107			} else {
108				Err(TypeError::InvalidNumberFormat {
109					target: Type::Uint,
110					fragment,
111				}
112				.into())
113			}
114		}
115	}
116}
117
118#[cfg(test)]
119pub mod tests {
120	use super::*;
121
122	#[test]
123	fn test_parse_uint_valid_zero() {
124		assert_eq!(parse_uint(Fragment::testing("0")).unwrap(), Uint::zero());
125	}
126
127	#[test]
128	fn test_parse_uint_valid_positive() {
129		let result = parse_uint(Fragment::testing("12345")).unwrap();
130		assert_eq!(format!("{}", result), "12345");
131	}
132
133	#[test]
134	fn test_parse_uint_large_positive() {
135		let large_num = "123456789012345678901234567890";
136		let result = parse_uint(Fragment::testing(large_num)).unwrap();
137		assert_eq!(format!("{}", result), large_num);
138	}
139
140	#[test]
141	fn test_parse_uint_scientific_notation() {
142		let result = parse_uint(Fragment::testing("1e5")).unwrap();
143		assert_eq!(format!("{}", result), "100000");
144	}
145
146	#[test]
147	fn test_parse_uint_scientific_decimal() {
148		let result = parse_uint(Fragment::testing("2.5e3")).unwrap();
149		assert_eq!(format!("{}", result), "2500");
150	}
151
152	#[test]
153	fn test_parse_uint_float_truncation() {
154		let result = parse_uint(Fragment::testing("123.789")).unwrap();
155		assert_eq!(format!("{}", result), "123");
156	}
157
158	#[test]
159	fn test_parse_uint_float_truncation_zero() {
160		let result = parse_uint(Fragment::testing("0.999")).unwrap();
161		assert_eq!(format!("{}", result), "0");
162	}
163
164	#[test]
165	fn test_parse_uint_with_underscores() {
166		let result = parse_uint(Fragment::testing("1_234_567")).unwrap();
167		assert_eq!(format!("{}", result), "1234567");
168	}
169
170	#[test]
171	fn test_parse_uint_with_leading_space() {
172		let result = parse_uint(Fragment::testing(" 12345")).unwrap();
173		assert_eq!(format!("{}", result), "12345");
174	}
175
176	#[test]
177	fn test_parse_uint_with_trailing_space() {
178		let result = parse_uint(Fragment::testing("12345 ")).unwrap();
179		assert_eq!(format!("{}", result), "12345");
180	}
181
182	#[test]
183	fn test_parse_uint_with_both_spaces() {
184		let result = parse_uint(Fragment::testing(" 12345 ")).unwrap();
185		assert_eq!(format!("{}", result), "12345");
186	}
187
188	#[test]
189	fn test_parse_uint_negative_integer() {
190		assert!(parse_uint(Fragment::testing("-12345")).is_err());
191	}
192
193	#[test]
194	fn test_parse_uint_negative_float() {
195		assert!(parse_uint(Fragment::testing("-123.45")).is_err());
196	}
197
198	#[test]
199	fn test_parse_uint_negative_scientific() {
200		assert!(parse_uint(Fragment::testing("-1e5")).is_err());
201	}
202
203	#[test]
204	fn test_parse_uint_negative_zero_float() {
205		// This should be handled gracefully - negative zero should
206		// become positive zero
207		let result = parse_uint(Fragment::testing("-0.0")).unwrap();
208		assert_eq!(format!("{}", result), "0");
209	}
210
211	#[test]
212	fn test_parse_uint_invalid_empty() {
213		assert!(parse_uint(Fragment::testing("")).is_err());
214	}
215
216	#[test]
217	fn test_parse_uint_invalid_whitespace() {
218		assert!(parse_uint(Fragment::testing("   ")).is_err());
219	}
220
221	#[test]
222	fn test_parse_uint_invalid_text() {
223		assert!(parse_uint(Fragment::testing("abc")).is_err());
224	}
225
226	#[test]
227	fn test_parse_uint_invalid_multiple_dots() {
228		assert!(parse_uint(Fragment::testing("1.2.3")).is_err());
229	}
230
231	#[test]
232	fn test_parse_uint_infinity() {
233		assert!(parse_uint(Fragment::testing("inf")).is_err());
234	}
235
236	#[test]
237	fn test_parse_uint_negative_infinity() {
238		assert!(parse_uint(Fragment::testing("-inf")).is_err());
239	}
240}