Skip to main content

reifydb_value/value/decimal/
parse.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 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, value_type::ValueType},
12};
13
14pub fn parse_decimal(fragment: Fragment) -> Result<Decimal, Error> {
15	let fragment_owned = fragment.clone();
16	let raw_value = fragment.text();
17
18	let needs_trimming = raw_value.as_bytes().first().is_some_and(|&b| b.is_ascii_whitespace())
19		|| raw_value.as_bytes().last().is_some_and(|&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 Err(TypeError::InvalidNumberFormat {
31			target: ValueType::Decimal,
32			fragment: fragment_owned,
33		}
34		.into());
35	}
36
37	let big_decimal = BigDecimalInner::from_str(&value).map_err(|_| -> Error {
38		TypeError::InvalidNumberFormat {
39			target: ValueType::Decimal,
40			fragment: fragment_owned,
41		}
42		.into()
43	})?;
44
45	Ok(Decimal::new(big_decimal))
46}
47
48#[cfg(test)]
49pub mod tests {
50	use super::*;
51
52	#[test]
53	fn test_parse_decimal_integer() {
54		let decimal = parse_decimal(Fragment::testing("123")).unwrap();
55		assert_eq!(decimal.to_string(), "123");
56	}
57
58	#[test]
59	fn test_parse_decimal_with_fractional() {
60		let decimal = parse_decimal(Fragment::testing("123.45")).unwrap();
61		assert_eq!(decimal.to_string(), "123.45");
62	}
63
64	#[test]
65	fn test_parse_decimal_with_underscores() {
66		let decimal = parse_decimal(Fragment::testing("1_234.56")).unwrap();
67		assert_eq!(decimal.to_string(), "1234.56");
68	}
69
70	#[test]
71	fn test_parse_decimal_negative() {
72		let decimal = parse_decimal(Fragment::testing("-123.45")).unwrap();
73		assert_eq!(decimal.to_string(), "-123.45");
74	}
75
76	#[test]
77	fn test_parse_decimal_empty() {
78		assert!(parse_decimal(Fragment::testing("")).is_err());
79	}
80
81	#[test]
82	fn test_parse_decimal_invalid() {
83		assert!(parse_decimal(Fragment::testing("not_a_number")).is_err());
84	}
85
86	#[test]
87	fn test_parse_decimal_scientific_notation() {
88		let decimal = parse_decimal(Fragment::testing("1.23e2")).unwrap();
89		assert_eq!(decimal.to_string(), "123");
90	}
91}