reifydb_type/value/uint/
parse.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the MIT, see license.md file
3
4use std::borrow::Cow;
5
6use num_bigint::BigInt;
7
8use crate::{
9	Error, IntoFragment, Type, err,
10	error::diagnostic::number::{invalid_number_format, number_out_of_range},
11	return_error,
12	value::uint::Uint,
13};
14
15pub fn parse_uint<'a>(fragment: impl IntoFragment<'a>) -> Result<Uint, Error> {
16	let fragment = fragment.into_fragment();
17	let raw_value = fragment.text();
18
19	// Fast path: check if we need any string processing
20	let needs_trimming = raw_value.as_bytes().first().map_or(false, |&b| b.is_ascii_whitespace())
21		|| raw_value.as_bytes().last().map_or(false, |&b| b.is_ascii_whitespace());
22
23	let has_underscores = raw_value.as_bytes().contains(&b'_');
24
25	let value = match (needs_trimming, has_underscores) {
26		(false, false) => Cow::Borrowed(raw_value), // Fast path -
27		// no processing
28		// needed
29		(true, false) => Cow::Borrowed(raw_value.trim()),
30		(false, true) => Cow::Owned(raw_value.replace('_', "")),
31		(true, true) => Cow::Owned(raw_value.trim().replace('_', "")),
32	};
33
34	if value.is_empty() {
35		return_error!(invalid_number_format(fragment, Type::Uint));
36	}
37
38	// Check for negative sign early, but allow -0.0 case to be handled by
39	// float parsing
40	if value.starts_with('-') && value != "-0.0" && value != "-0" {
41		// Quick check for other obvious negative values
42		if let Ok(bigint) = value.parse::<BigInt>() {
43			if bigint.sign() == num_bigint::Sign::Minus {
44				return_error!(number_out_of_range(fragment, Type::Uint, None));
45			}
46		}
47		// For non-BigInt parseable values, let float parsing handle it
48	}
49
50	// Try parsing as BigInt first
51	match value.parse::<BigInt>() {
52		Ok(v) => {
53			// Double check that the BigInt is non-negative (should
54			// be guaranteed by the prefix check)
55			if v.sign() == num_bigint::Sign::Minus {
56				return_error!(number_out_of_range(fragment, Type::Uint, None));
57			}
58			Ok(Uint::from(v))
59		}
60		Err(_) => {
61			// If BigInt parsing fails, try parsing as f64 for
62			// scientific notation and truncation
63			if let Ok(f) = value.parse::<f64>() {
64				if f.is_infinite() {
65					err!(number_out_of_range(fragment, Type::Uint, None))
66				} else {
67					let truncated = f.trunc();
68					// Handle negative zero and other
69					// negative values
70					if truncated < 0.0 && truncated != -0.0 {
71						return_error!(number_out_of_range(fragment, Type::Uint, None));
72					}
73					// Convert the truncated float to
74					// BigInt, treating -0.0 as 0.0
75					let abs_truncated = if truncated == -0.0 {
76						0.0
77					} else {
78						truncated
79					};
80					if let Ok(bigint) = format!("{:.0}", abs_truncated).parse::<BigInt>() {
81						Ok(Uint::from(bigint))
82					} else {
83						err!(invalid_number_format(fragment, Type::Uint))
84					}
85				}
86			} else {
87				// Check if it contains a minus sign to provide
88				// better error message
89				if value.contains('-') {
90					err!(number_out_of_range(fragment, Type::Uint, None))
91				} else {
92					err!(invalid_number_format(fragment, Type::Uint))
93				}
94			}
95		}
96	}
97}
98
99#[cfg(test)]
100mod tests {
101	use super::*;
102	use crate::OwnedFragment;
103
104	#[test]
105	fn test_parse_uint_valid_zero() {
106		assert_eq!(parse_uint(OwnedFragment::testing("0")).unwrap(), Uint::zero());
107	}
108
109	#[test]
110	fn test_parse_uint_valid_positive() {
111		let result = parse_uint(OwnedFragment::testing("12345")).unwrap();
112		assert_eq!(format!("{}", result), "12345");
113	}
114
115	#[test]
116	fn test_parse_uint_large_positive() {
117		let large_num = "123456789012345678901234567890";
118		let result = parse_uint(OwnedFragment::testing(large_num)).unwrap();
119		assert_eq!(format!("{}", result), large_num);
120	}
121
122	#[test]
123	fn test_parse_uint_scientific_notation() {
124		let result = parse_uint(OwnedFragment::testing("1e5")).unwrap();
125		assert_eq!(format!("{}", result), "100000");
126	}
127
128	#[test]
129	fn test_parse_uint_scientific_decimal() {
130		let result = parse_uint(OwnedFragment::testing("2.5e3")).unwrap();
131		assert_eq!(format!("{}", result), "2500");
132	}
133
134	#[test]
135	fn test_parse_uint_float_truncation() {
136		let result = parse_uint(OwnedFragment::testing("123.789")).unwrap();
137		assert_eq!(format!("{}", result), "123");
138	}
139
140	#[test]
141	fn test_parse_uint_float_truncation_zero() {
142		let result = parse_uint(OwnedFragment::testing("0.999")).unwrap();
143		assert_eq!(format!("{}", result), "0");
144	}
145
146	#[test]
147	fn test_parse_uint_with_underscores() {
148		let result = parse_uint(OwnedFragment::testing("1_234_567")).unwrap();
149		assert_eq!(format!("{}", result), "1234567");
150	}
151
152	#[test]
153	fn test_parse_uint_with_leading_space() {
154		let result = parse_uint(OwnedFragment::testing(" 12345")).unwrap();
155		assert_eq!(format!("{}", result), "12345");
156	}
157
158	#[test]
159	fn test_parse_uint_with_trailing_space() {
160		let result = parse_uint(OwnedFragment::testing("12345 ")).unwrap();
161		assert_eq!(format!("{}", result), "12345");
162	}
163
164	#[test]
165	fn test_parse_uint_with_both_spaces() {
166		let result = parse_uint(OwnedFragment::testing(" 12345 ")).unwrap();
167		assert_eq!(format!("{}", result), "12345");
168	}
169
170	#[test]
171	fn test_parse_uint_negative_integer() {
172		assert!(parse_uint(OwnedFragment::testing("-12345")).is_err());
173	}
174
175	#[test]
176	fn test_parse_uint_negative_float() {
177		assert!(parse_uint(OwnedFragment::testing("-123.45")).is_err());
178	}
179
180	#[test]
181	fn test_parse_uint_negative_scientific() {
182		assert!(parse_uint(OwnedFragment::testing("-1e5")).is_err());
183	}
184
185	#[test]
186	fn test_parse_uint_negative_zero_float() {
187		// This should be handled gracefully - negative zero should
188		// become positive zero
189		let result = parse_uint(OwnedFragment::testing("-0.0")).unwrap();
190		assert_eq!(format!("{}", result), "0");
191	}
192
193	#[test]
194	fn test_parse_uint_invalid_empty() {
195		assert!(parse_uint(OwnedFragment::testing("")).is_err());
196	}
197
198	#[test]
199	fn test_parse_uint_invalid_whitespace() {
200		assert!(parse_uint(OwnedFragment::testing("   ")).is_err());
201	}
202
203	#[test]
204	fn test_parse_uint_invalid_text() {
205		assert!(parse_uint(OwnedFragment::testing("abc")).is_err());
206	}
207
208	#[test]
209	fn test_parse_uint_invalid_multiple_dots() {
210		assert!(parse_uint(OwnedFragment::testing("1.2.3")).is_err());
211	}
212
213	#[test]
214	fn test_parse_uint_infinity() {
215		assert!(parse_uint(OwnedFragment::testing("inf")).is_err());
216	}
217
218	#[test]
219	fn test_parse_uint_negative_infinity() {
220		assert!(parse_uint(OwnedFragment::testing("-inf")).is_err());
221	}
222}