diff --git a/crates/immo-core/src/euros.rs b/crates/immo-core/src/euros.rs index 0839ba0..1f38da0 100644 --- a/crates/immo-core/src/euros.rs +++ b/crates/immo-core/src/euros.rs @@ -1,6 +1,15 @@ use std::fmt; -use rust_decimal::Decimal; +use rust_decimal::{Decimal, prelude::ToPrimitive}; +use thiserror::Error; + +#[derive(Debug, PartialEq, Eq, Error, Clone)] +pub enum EurosError { + #[error("Can only create from decimal with scale = 2: {0}")] + WrongDecimalScale(Decimal), + #[error("Decimal cannot be represented as i64: {0}")] + DecimalIsTooBig(Decimal), +} #[derive(PartialOrd, PartialEq, Ord, Eq, Clone, Copy, Debug)] pub struct Euros { @@ -12,6 +21,18 @@ impl Euros { Euros { value_as_cents } } + pub fn from_euros_as_decimal(value_as_euros: Decimal) -> Result { + // Euros should explicitely have scale 2 to validate cents precision + if value_as_euros.scale() != 2 { + Err(EurosError::WrongDecimalScale(value_as_euros)) + } else { + (value_as_euros * Decimal::ONE_HUNDRED) + .to_i64() + .map(Euros::from_cents_as_i64) + .ok_or(EurosError::DecimalIsTooBig(value_as_euros)) + } + } + pub const fn as_cents(self) -> i64 { self.value_as_cents } @@ -106,4 +127,37 @@ mod tests { let euros = Euros::from_cents_as_i64(-123_00); assert_eq!(format!("{}", euros), "-123.00 €"); } + + #[test] + fn from_euros_as_decimal_fails_if_scale_is_larger_than_2() { + let dec = Decimal::new(3141, 3); + let ret = Euros::from_euros_as_decimal(dec); + assert!(ret.is_err()); + assert_eq!(ret.unwrap_err(), EurosError::WrongDecimalScale(dec)); + } + + #[test] + fn from_euros_as_decimal_fails_if_scale_is_lower_than_2() { + let dec = Decimal::new(3141, 1); + let ret = Euros::from_euros_as_decimal(dec); + assert!(ret.is_err()); + assert_eq!(ret.unwrap_err(), EurosError::WrongDecimalScale(dec)); + } + + #[test] + fn from_euros_as_decimal_yields_correct_result_if_scale_is_2() { + let dec = Decimal::new(3141, 2); + let ret = Euros::from_euros_as_decimal(dec); + assert!(ret.is_ok()); + assert_eq!(ret.unwrap().as_cents(), 3141); + } + + #[test] + fn from_euros_as_decimal_yields_error_with_decimal_too_big() { + let mut dec = Decimal::from(u64::MAX); + dec.rescale(2); + let ret = Euros::from_euros_as_decimal(dec); + assert!(ret.is_err()); + assert_eq!(ret.unwrap_err(), EurosError::DecimalIsTooBig(dec)); + } }