use core::{
fmt,
ops::{Add, Sub},
};
use serde::{Deserialize, Serialize};
use crate::error::AmountError;
pub const SUN_PER_TRX: i64 = 1_000_000;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Trx(i64);
impl Trx {
pub const ZERO: Trx = Trx(0);
pub const fn from_sun_unchecked(sun: i64) -> Self {
Self(sun)
}
pub fn from_sun(sun: i64) -> Result<Self, AmountError> {
if sun < 0 {
return Err(AmountError::Negative(sun));
}
Ok(Self(sun))
}
pub fn from_trx(trx: f64) -> Result<Self, AmountError> {
if !trx.is_finite() || trx < 0.0 {
return Err(AmountError::OutOfRange(trx));
}
let sun = trx * SUN_PER_TRX as f64;
if sun > i64::MAX as f64 {
return Err(AmountError::OutOfRange(trx));
}
Ok(Self(sun.round() as i64))
}
pub const fn as_sun(self) -> i64 {
self.0
}
pub fn as_trx(self) -> f64 {
self.0 as f64 / SUN_PER_TRX as f64
}
pub fn checked_add(self, rhs: Trx) -> Option<Trx> {
self.0.checked_add(rhs.0).map(Trx)
}
pub fn checked_sub(self, rhs: Trx) -> Option<Trx> {
self.0.checked_sub(rhs.0).map(Trx)
}
}
impl Add for Trx {
type Output = Trx;
fn add(self, rhs: Trx) -> Trx {
Trx(self.0.saturating_add(rhs.0))
}
}
impl Sub for Trx {
type Output = Trx;
fn sub(self, rhs: Trx) -> Trx {
Trx(self.0.saturating_sub(rhs.0))
}
}
impl fmt::Display for Trx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} TRX", self.as_trx())
}
}
impl fmt::Debug for Trx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Trx({} sun)", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conversions() {
assert_eq!(Trx::from_trx(1.0).unwrap().as_sun(), 1_000_000);
assert_eq!(Trx::from_trx(1.5).unwrap().as_sun(), 1_500_000);
assert_eq!(Trx::from_sun(2_500_000).unwrap().as_trx(), 2.5);
}
#[test]
fn rejects_negative() {
assert!(Trx::from_sun(-1).is_err());
assert!(Trx::from_trx(-1.0).is_err());
assert!(Trx::from_trx(f64::NAN).is_err());
}
#[test]
fn unchecked_allows_negative() {
assert_eq!(Trx::from_sun_unchecked(-5).as_sun(), -5);
}
#[test]
fn arithmetic() {
let a = Trx::from_trx(1.0).unwrap();
let b = Trx::from_trx(0.5).unwrap();
assert_eq!((a + b).as_sun(), 1_500_000);
assert_eq!((a - b).as_sun(), 500_000);
assert_eq!(a.checked_add(b), Some(Trx::from_sun(1_500_000).unwrap()));
}
}