use crate::{
decode::Decode,
encode::{Encode, IsNull},
error::BoxDynError,
types::Type,
{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres},
};
use byteorder::{BigEndian, ByteOrder};
use std::{
io,
ops::{Add, AddAssign, Sub, SubAssign},
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub struct PgMoney(
), this will be the value in whole cents.
pub i64,
);
impl PgMoney {
#[cfg(feature = "bigdecimal")]
pub fn to_bigdecimal(self, locale_frac_digits: i64) -> bigdecimal::BigDecimal {
let digits = num_bigint::BigInt::from(self.0);
bigdecimal::BigDecimal::new(digits, locale_frac_digits)
}
#[cfg(feature = "rust_decimal")]
pub fn to_decimal(self, locale_frac_digits: u32) -> rust_decimal::Decimal {
rust_decimal::Decimal::new(self.0, locale_frac_digits)
}
#[cfg(feature = "rust_decimal")]
pub fn from_decimal(mut decimal: rust_decimal::Decimal, locale_frac_digits: u32) -> Self {
decimal.rescale(locale_frac_digits);
const SIGN_MASK: i64 = i64::MAX;
let is_negative = decimal.is_sign_negative();
let serialized = decimal.serialize();
let value = i64::from_le_bytes(
*<&[u8; 8]>::try_from(&serialized[4..12])
.expect("BUG: slice of serialized should be 8 bytes"),
) & SIGN_MASK;
Self(if is_negative { -value } else { value })
}
#[cfg(feature = "bigdecimal")]
pub fn from_bigdecimal(
decimal: bigdecimal::BigDecimal,
locale_frac_digits: u32,
) -> Result<Self, BoxDynError> {
use bigdecimal::ToPrimitive;
let multiplier = bigdecimal::BigDecimal::new(
num_bigint::BigInt::from(10i128.pow(locale_frac_digits)),
0,
);
let cents = decimal * multiplier;
let money = cents.to_i64().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"Provided BigDecimal could not convert to i64: overflow.",
)
})?;
Ok(Self(money))
}
}
impl Type<Postgres> for PgMoney {
fn type_info() -> PgTypeInfo {
PgTypeInfo::MONEY
}
}
impl PgHasArrayType for PgMoney {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::MONEY_ARRAY
}
}
impl<T> From<T> for PgMoney
where
T: Into<i64>,
{
fn from(num: T) -> Self {
Self(num.into())
}
}
impl Encode<'_, Postgres> for PgMoney {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.0.to_be_bytes());
Ok(IsNull::No)
}
}
impl Decode<'_, Postgres> for PgMoney {
fn decode(value: PgValueRef<'_>) -> Result<Self, BoxDynError> {
match value.format() {
PgValueFormat::Binary => {
let cents = BigEndian::read_i64(value.as_bytes()?);
Ok(PgMoney(cents))
}
PgValueFormat::Text => {
let error = io::Error::new(
io::ErrorKind::InvalidData,
"Reading a `MONEY` value in text format is not supported.",
);
Err(Box::new(error))
}
}
}
}
impl Add<PgMoney> for PgMoney {
type Output = PgMoney;
fn add(self, rhs: PgMoney) -> Self::Output {
self.0
.checked_add(rhs.0)
.map(PgMoney)
.expect("overflow adding money amounts")
}
}
impl AddAssign<PgMoney> for PgMoney {
fn add_assign(&mut self, rhs: PgMoney) {
self.0 = self
.0
.checked_add(rhs.0)
.expect("overflow adding money amounts")
}
}
impl Sub<PgMoney> for PgMoney {
type Output = PgMoney;
fn sub(self, rhs: PgMoney) -> Self::Output {
self.0
.checked_sub(rhs.0)
.map(PgMoney)
.expect("overflow subtracting money amounts")
}
}
impl SubAssign<PgMoney> for PgMoney {
fn sub_assign(&mut self, rhs: PgMoney) {
self.0 = self
.0
.checked_sub(rhs.0)
.expect("overflow subtracting money amounts")
}
}
#[cfg(test)]
mod tests {
use super::PgMoney;
#[test]
fn adding_works() {
assert_eq!(PgMoney(3), PgMoney(1) + PgMoney(2))
}
#[test]
fn add_assign_works() {
let mut money = PgMoney(1);
money += PgMoney(2);
assert_eq!(PgMoney(3), money);
}
#[test]
fn subtracting_works() {
assert_eq!(PgMoney(4), PgMoney(5) - PgMoney(1))
}
#[test]
fn sub_assign_works() {
let mut money = PgMoney(1);
money -= PgMoney(2);
assert_eq!(PgMoney(-1), money);
}
#[test]
fn default_value() {
let money = PgMoney::default();
assert_eq!(money, PgMoney(0));
}
#[test]
#[should_panic]
fn add_overflow_panics() {
let _ = PgMoney(i64::MAX) + PgMoney(1);
}
#[test]
#[should_panic]
fn add_assign_overflow_panics() {
let mut money = PgMoney(i64::MAX);
money += PgMoney(1);
}
#[test]
#[should_panic]
fn sub_overflow_panics() {
let _ = PgMoney(i64::MIN) - PgMoney(1);
}
#[test]
#[should_panic]
fn sub_assign_overflow_panics() {
let mut money = PgMoney(i64::MIN);
money -= PgMoney(1);
}
#[test]
#[cfg(feature = "bigdecimal")]
fn conversion_to_bigdecimal_works() {
let money = PgMoney(12345);
assert_eq!(
bigdecimal::BigDecimal::new(num_bigint::BigInt::from(12345), 2),
money.to_bigdecimal(2)
);
}
#[test]
#[cfg(feature = "rust_decimal")]
fn conversion_to_decimal_works() {
assert_eq!(
rust_decimal::Decimal::new(12345, 2),
PgMoney(12345).to_decimal(2)
);
}
#[test]
#[cfg(feature = "rust_decimal")]
fn conversion_from_decimal_works() {
assert_eq!(
PgMoney(12345),
PgMoney::from_decimal(rust_decimal::Decimal::new(12345, 2), 2)
);
assert_eq!(
PgMoney(12345),
PgMoney::from_decimal(rust_decimal::Decimal::new(123450, 3), 2)
);
assert_eq!(
PgMoney(-12345),
PgMoney::from_decimal(rust_decimal::Decimal::new(-123450, 3), 2)
);
assert_eq!(
PgMoney(-12300),
PgMoney::from_decimal(rust_decimal::Decimal::new(-123, 0), 2)
);
}
#[test]
#[cfg(feature = "bigdecimal")]
fn conversion_from_bigdecimal_works() {
let dec = bigdecimal::BigDecimal::new(num_bigint::BigInt::from(12345), 2);
assert_eq!(PgMoney(12345), PgMoney::from_bigdecimal(dec, 2).unwrap());
}
}