reifydb_type/value/int/
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, Fragment, Type, err,
10	error::diagnostic::number::{invalid_number_format, number_out_of_range},
11	return_error,
12	value::int::Int,
13};
14
15pub fn parse_int(fragment: Fragment) -> Result<Int, Error> {
16	// Fragment is already owned, no conversion needed
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	let has_underscores = raw_value.as_bytes().contains(&b'_');
23
24	let value = match (needs_trimming, has_underscores) {
25		(false, false) => Cow::Borrowed(raw_value), // Fast path -
26		// no processing
27		// needed
28		(true, false) => Cow::Borrowed(raw_value.trim()),
29		(false, true) => Cow::Owned(raw_value.replace('_', "")),
30		(true, true) => Cow::Owned(raw_value.trim().replace('_', "")),
31	};
32
33	if value.is_empty() {
34		return_error!(invalid_number_format(fragment, Type::Int));
35	}
36
37	// Try parsing as BigInt first
38	match value.parse::<BigInt>() {
39		Ok(v) => Ok(Int::from(v)),
40		Err(_) => {
41			// If BigInt parsing fails, try parsing as f64 for
42			// scientific notation and truncation
43			if let Ok(f) = value.parse::<f64>() {
44				if f.is_infinite() {
45					err!(number_out_of_range(fragment, Type::Int, None))
46				} else {
47					let truncated = f.trunc();
48					// Convert the truncated float to BigInt
49					if let Ok(bigint) = format!("{:.0}", truncated).parse::<BigInt>() {
50						Ok(Int::from(bigint))
51					} else {
52						err!(invalid_number_format(fragment, Type::Int))
53					}
54				}
55			} else {
56				err!(invalid_number_format(fragment, Type::Int))
57			}
58		}
59	}
60}
61
62#[cfg(test)]
63mod tests {
64	use super::*;
65	use crate::Fragment;
66
67	#[test]
68	fn test_parse_int_valid_zero() {
69		assert_eq!(parse_int(Fragment::testing("0")).unwrap(), Int::zero());
70	}
71
72	#[test]
73	fn test_parse_int_valid_positive() {
74		let result = parse_int(Fragment::testing("12345")).unwrap();
75		assert_eq!(format!("{}", result), "12345");
76	}
77
78	#[test]
79	fn test_parse_int_valid_negative() {
80		let result = parse_int(Fragment::testing("-12345")).unwrap();
81		assert_eq!(format!("{}", result), "-12345");
82	}
83
84	#[test]
85	fn test_parse_int_large_positive() {
86		let large_num = "123456789012345678901234567890";
87		let result = parse_int(Fragment::testing(large_num)).unwrap();
88		assert_eq!(format!("{}", result), large_num);
89	}
90
91	#[test]
92	fn test_parse_int_large_negative() {
93		let large_num = "-123456789012345678901234567890";
94		let result = parse_int(Fragment::testing(large_num)).unwrap();
95		assert_eq!(format!("{}", result), large_num);
96	}
97
98	#[test]
99	fn test_parse_int_scientific_notation() {
100		let result = parse_int(Fragment::testing("1e5")).unwrap();
101		assert_eq!(format!("{}", result), "100000");
102	}
103
104	#[test]
105	fn test_parse_int_scientific_negative() {
106		let result = parse_int(Fragment::testing("-1.5e3")).unwrap();
107		assert_eq!(format!("{}", result), "-1500");
108	}
109
110	#[test]
111	fn test_parse_int_float_truncation() {
112		let result = parse_int(Fragment::testing("123.789")).unwrap();
113		assert_eq!(format!("{}", result), "123");
114	}
115
116	#[test]
117	fn test_parse_int_float_truncation_negative() {
118		let result = parse_int(Fragment::testing("-123.789")).unwrap();
119		assert_eq!(format!("{}", result), "-123");
120	}
121
122	#[test]
123	fn test_parse_int_with_underscores() {
124		let result = parse_int(Fragment::testing("1_234_567")).unwrap();
125		assert_eq!(format!("{}", result), "1234567");
126	}
127
128	#[test]
129	fn test_parse_int_with_leading_space() {
130		let result = parse_int(Fragment::testing(" 12345")).unwrap();
131		assert_eq!(format!("{}", result), "12345");
132	}
133
134	#[test]
135	fn test_parse_int_with_trailing_space() {
136		let result = parse_int(Fragment::testing("12345 ")).unwrap();
137		assert_eq!(format!("{}", result), "12345");
138	}
139
140	#[test]
141	fn test_parse_int_with_both_spaces() {
142		let result = parse_int(Fragment::testing(" -12345 ")).unwrap();
143		assert_eq!(format!("{}", result), "-12345");
144	}
145
146	#[test]
147	fn test_parse_int_invalid_empty() {
148		assert!(parse_int(Fragment::testing("")).is_err());
149	}
150
151	#[test]
152	fn test_parse_int_invalid_whitespace() {
153		assert!(parse_int(Fragment::testing("   ")).is_err());
154	}
155
156	#[test]
157	fn test_parse_int_invalid_text() {
158		assert!(parse_int(Fragment::testing("abc")).is_err());
159	}
160
161	#[test]
162	fn test_parse_int_invalid_multiple_dots() {
163		assert!(parse_int(Fragment::testing("1.2.3")).is_err());
164	}
165
166	#[test]
167	fn test_parse_int_infinity() {
168		assert!(parse_int(Fragment::testing("inf")).is_err());
169	}
170
171	#[test]
172	fn test_parse_int_negative_infinity() {
173		assert!(parse_int(Fragment::testing("-inf")).is_err());
174	}
175}