Skip to main content

reifydb_type/value/decimal/
parse.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 ReifyDB
3
4use std::{borrow::Cow, str::FromStr};
5
6use bigdecimal::BigDecimal as BigDecimalInner;
7
8use crate::{
9	error::{Error, TypeError},
10	fragment::Fragment,
11	value::{decimal::Decimal, r#type::Type},
12};
13
14pub fn parse_decimal(fragment: Fragment) -> Result<Decimal, Error> {
15	// Fragment is already owned, no conversion needed
16	let fragment_owned = fragment.clone();
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),
26		(true, false) => Cow::Borrowed(raw_value.trim()),
27		(false, true) => Cow::Owned(raw_value.replace('_', "")),
28		(true, true) => Cow::Owned(raw_value.trim().replace('_', "")),
29	};
30
31	if value.is_empty() {
32		return Err(TypeError::InvalidNumberFormat {
33			target: Type::Decimal,
34			fragment: fragment_owned,
35		}
36		.into());
37	}
38
39	let big_decimal = BigDecimalInner::from_str(&value).map_err(|_| -> Error {
40		TypeError::InvalidNumberFormat {
41			target: Type::Decimal,
42			fragment: fragment_owned,
43		}
44		.into()
45	})?;
46
47	Ok(Decimal::new(big_decimal))
48}
49
50#[cfg(test)]
51pub mod tests {
52	use super::*;
53
54	#[test]
55	fn test_parse_decimal_integer() {
56		let decimal = parse_decimal(Fragment::testing("123")).unwrap();
57		assert_eq!(decimal.to_string(), "123");
58	}
59
60	#[test]
61	fn test_parse_decimal_with_fractional() {
62		let decimal = parse_decimal(Fragment::testing("123.45")).unwrap();
63		assert_eq!(decimal.to_string(), "123.45");
64	}
65
66	#[test]
67	fn test_parse_decimal_with_underscores() {
68		let decimal = parse_decimal(Fragment::testing("1_234.56")).unwrap();
69		assert_eq!(decimal.to_string(), "1234.56");
70	}
71
72	#[test]
73	fn test_parse_decimal_negative() {
74		let decimal = parse_decimal(Fragment::testing("-123.45")).unwrap();
75		assert_eq!(decimal.to_string(), "-123.45");
76	}
77
78	#[test]
79	fn test_parse_decimal_empty() {
80		assert!(parse_decimal(Fragment::testing("")).is_err());
81	}
82
83	#[test]
84	fn test_parse_decimal_invalid() {
85		assert!(parse_decimal(Fragment::testing("not_a_number")).is_err());
86	}
87
88	#[test]
89	fn test_parse_decimal_scientific_notation() {
90		let decimal = parse_decimal(Fragment::testing("1.23e2")).unwrap();
91		assert_eq!(decimal.to_string(), "123");
92	}
93}