reifydb_type/value/decimal/
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, str::FromStr};
5
6use bigdecimal::BigDecimal as BigDecimalInner;
7
8use crate::{
9	Error, Fragment, Type, error::diagnostic::number::invalid_number_format, return_error, value::decimal::Decimal,
10};
11
12pub fn parse_decimal(fragment: Fragment) -> Result<Decimal, Error> {
13	// Fragment is already owned, no conversion needed
14	let fragment_owned = fragment.clone();
15	let raw_value = fragment.text();
16
17	// Fast path: check if we need any string processing
18	let needs_trimming = raw_value.as_bytes().first().map_or(false, |&b| b.is_ascii_whitespace())
19		|| raw_value.as_bytes().last().map_or(false, |&b| b.is_ascii_whitespace());
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		(true, false) => Cow::Borrowed(raw_value.trim()),
25		(false, true) => Cow::Owned(raw_value.replace('_', "")),
26		(true, true) => Cow::Owned(raw_value.trim().replace('_', "")),
27	};
28
29	if value.is_empty() {
30		return_error!(invalid_number_format(fragment_owned.clone(), Type::Decimal));
31	}
32
33	let big_decimal = BigDecimalInner::from_str(&value)
34		.map_err(|_| crate::error!(invalid_number_format(fragment_owned, Type::Decimal)))?;
35
36	Ok(Decimal::new(big_decimal))
37}
38
39#[cfg(test)]
40mod tests {
41	use super::*;
42	use crate::Fragment;
43
44	#[test]
45	fn test_parse_decimal_integer() {
46		let decimal = parse_decimal(Fragment::testing("123")).unwrap();
47		assert_eq!(decimal.to_string(), "123");
48	}
49
50	#[test]
51	fn test_parse_decimal_with_fractional() {
52		let decimal = parse_decimal(Fragment::testing("123.45")).unwrap();
53		assert_eq!(decimal.to_string(), "123.45");
54	}
55
56	#[test]
57	fn test_parse_decimal_with_underscores() {
58		let decimal = parse_decimal(Fragment::testing("1_234.56")).unwrap();
59		assert_eq!(decimal.to_string(), "1234.56");
60	}
61
62	#[test]
63	fn test_parse_decimal_negative() {
64		let decimal = parse_decimal(Fragment::testing("-123.45")).unwrap();
65		assert_eq!(decimal.to_string(), "-123.45");
66	}
67
68	#[test]
69	fn test_parse_decimal_empty() {
70		assert!(parse_decimal(Fragment::testing("")).is_err());
71	}
72
73	#[test]
74	fn test_parse_decimal_invalid() {
75		assert!(parse_decimal(Fragment::testing("not_a_number")).is_err());
76	}
77
78	#[test]
79	fn test_parse_decimal_scientific_notation() {
80		let decimal = parse_decimal(Fragment::testing("1.23e2")).unwrap();
81		assert_eq!(decimal.to_string(), "123");
82	}
83}