use crate::error::{Error, Result};
use rust_decimal::Decimal;
use std::str::FromStr;
pub fn parse_decimal(s: &str) -> Result<Decimal> {
Decimal::from_str(s.trim())
.map_err(|e| Error::Deserialization(format!("invalid decimal {s:?}: {e}")))
}
pub fn parse_opt_decimal(s: Option<&str>) -> Result<Option<Decimal>> {
match s.map(str::trim) {
None | Some("" | "null") => Ok(None),
Some(value) => parse_decimal(value).map(Some),
}
}
#[must_use]
pub fn format_decimal(value: Decimal) -> String {
value.normalize().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn parses_clean_and_padded_strings() {
assert_eq!(parse_decimal("0.00010000").unwrap(), dec!(0.0001));
assert_eq!(parse_decimal(" 42 ").unwrap(), dec!(42));
assert_eq!(parse_decimal("-1.5").unwrap(), dec!(-1.5));
}
#[test]
fn rejects_invalid_decimals() {
assert!(matches!(
parse_decimal("not-a-number").unwrap_err(),
Error::Deserialization(_)
));
assert!(parse_decimal("").is_err());
}
#[test]
fn optional_decimal_handles_absent_and_null() {
assert_eq!(parse_opt_decimal(None).unwrap(), None);
assert_eq!(parse_opt_decimal(Some("")).unwrap(), None);
assert_eq!(parse_opt_decimal(Some("null")).unwrap(), None);
assert_eq!(parse_opt_decimal(Some("1.25")).unwrap(), Some(dec!(1.25)));
assert!(parse_opt_decimal(Some("bad")).is_err());
}
#[test]
fn formats_without_scientific_notation_or_trailing_zeros() {
assert_eq!(format_decimal(dec!(1.2300)), "1.23");
assert_eq!(format_decimal(dec!(5.0)), "5");
assert_eq!(format_decimal(dec!(0.00010000)), "0.0001");
assert_eq!(format_decimal(dec!(20000)), "20000");
}
}