Skip to main content

reifydb_type/value/int/
parse.rs

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