o2_sdk/
decimal.rs

1/// A non-negative decimal type for prices and quantities.
2///
3/// Wraps [`rust_decimal::Decimal`] with the invariant that the value is always >= 0.
4/// There is no `From<f64>` implementation to prevent accidental precision loss.
5/// Serializes as a string in JSON.
6use std::fmt;
7use std::ops::{Add, Div, Mul};
8use std::str::FromStr;
9
10use rust_decimal::Decimal;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13use crate::errors::O2Error;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct UnsignedDecimal(Decimal);
17
18impl UnsignedDecimal {
19    /// The one value.
20    pub const ONE: Self = Self(Decimal::ONE);
21
22    /// The zero value (constant form).
23    pub const ZERO: Self = Self(Decimal::ZERO);
24
25    /// Create a new `UnsignedDecimal`, returning an error if the value is negative.
26    pub fn new(value: Decimal) -> Result<Self, O2Error> {
27        if value.is_sign_negative() && !value.is_zero() {
28            return Err(O2Error::Other(format!(
29                "UnsignedDecimal cannot be negative: {value}"
30            )));
31        }
32        Ok(Self(value))
33    }
34
35    /// The zero value.
36    pub fn zero() -> Self {
37        Self(Decimal::ZERO)
38    }
39
40    /// Fallible subtraction — returns an error if the result would be negative.
41    pub fn try_sub(&self, other: Self) -> Result<Self, O2Error> {
42        Self::new(self.0 - other.0)
43    }
44
45    /// Access the inner `Decimal`.
46    pub fn inner(&self) -> &Decimal {
47        &self.0
48    }
49
50    /// Consume and return the inner `Decimal`.
51    pub fn into_inner(self) -> Decimal {
52        self.0
53    }
54}
55
56impl fmt::Display for UnsignedDecimal {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "{}", self.0)
59    }
60}
61
62impl FromStr for UnsignedDecimal {
63    type Err = O2Error;
64
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        let d = Decimal::from_str(s)
67            .map_err(|e| O2Error::Other(format!("Invalid decimal '{s}': {e}")))?;
68        Self::new(d)
69    }
70}
71
72impl Serialize for UnsignedDecimal {
73    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
74        serializer.serialize_str(&self.0.to_string())
75    }
76}
77
78impl<'de> Deserialize<'de> for UnsignedDecimal {
79    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
80        let s = String::deserialize(deserializer)?;
81        let d = Decimal::from_str(&s).map_err(serde::de::Error::custom)?;
82        Self::new(d).map_err(serde::de::Error::custom)
83    }
84}
85
86impl From<u64> for UnsignedDecimal {
87    fn from(v: u64) -> Self {
88        Self(Decimal::from(v))
89    }
90}
91
92impl From<u32> for UnsignedDecimal {
93    fn from(v: u32) -> Self {
94        Self(Decimal::from(v))
95    }
96}
97
98impl Add for UnsignedDecimal {
99    type Output = Self;
100    fn add(self, rhs: Self) -> Self::Output {
101        Self(self.0 + rhs.0)
102    }
103}
104
105impl Mul for UnsignedDecimal {
106    type Output = Self;
107    fn mul(self, rhs: Self) -> Self::Output {
108        Self(self.0 * rhs.0)
109    }
110}
111
112impl Div for UnsignedDecimal {
113    type Output = Self;
114    fn div(self, rhs: Self) -> Self::Output {
115        Self(self.0 / rhs.0)
116    }
117}