ocpi_tariffs/
number.rs

1//! Numerical OCPI types.
2use std::{borrow::Cow, fmt};
3
4use rust_decimal::Decimal;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::{
8    into_caveat, json,
9    warning::{self, IntoCaveat},
10};
11
12/// <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#14-number-type>
13const DECIMAL_SCALE: u32 = 4;
14
15/// The warnings that can happen when parsing or linting a numerical value.
16#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
17pub enum WarningKind {
18    /// Neither the timezone or country field require char escape codes.
19    ContainsEscapeCodes,
20
21    /// The value provided exceeds `Decimal::MAX`.
22    ExceedsMaximumPossibleValue,
23
24    /// This error should not happen.
25    Internal(String),
26
27    /// The JSON value given is not a number.
28    InvalidType,
29
30    /// The value provided is less than `Decimal::MIN`.
31    LessThanMinimumPossibleValue,
32
33    /// An underflow is when there are more fractional digits than can be represented within `Decimal`.
34    Underflow,
35}
36
37impl fmt::Display for WarningKind {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self {
40            WarningKind::ContainsEscapeCodes => write!(
41                f,
42                "The value contains escape codes but it does not need them"
43            ),
44            WarningKind::InvalidType => write!(f, "The value should be a number."),
45            WarningKind::ExceedsMaximumPossibleValue => {
46                write!(
47                    f,
48                    "The value provided exceeds `79,228,162,514,264,337,593,543,950,335`."
49                )
50            }
51            WarningKind::LessThanMinimumPossibleValue => write!(
52                f,
53                "The value provided is less than `-79,228,162,514,264,337,593,543,950,335`."
54            ),
55            WarningKind::Underflow => write!(
56                f,
57                "An underflow is when there are more than 28 fractional digits"
58            ),
59            WarningKind::Internal(msg) => write!(f, "{msg}"),
60        }
61    }
62}
63
64impl warning::Kind for WarningKind {
65    fn id(&self) -> Cow<'static, str> {
66        match self {
67            WarningKind::ContainsEscapeCodes => "contains_escape_codes".into(),
68            WarningKind::InvalidType => "invalid_type".into(),
69            WarningKind::ExceedsMaximumPossibleValue => "exceeds_maximum_possible_value".into(),
70            WarningKind::LessThanMinimumPossibleValue => "less_than_minimum_possible_value".into(),
71            WarningKind::Underflow => "underflow".into(),
72            WarningKind::Internal(_) => "<internal_error>".into(),
73        }
74    }
75}
76/// A decimal number that will serialize to the precision defined in the OCPI spec.
77///  
78/// <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#14-number-type>
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
80pub struct Number(Decimal);
81
82impl json::FromJson<'_, '_> for Number {
83    type WarningKind = WarningKind;
84
85    fn from_json(elem: &json::Element<'_>) -> crate::Verdict<Self, Self::WarningKind> {
86        let mut warnings = warning::Set::new();
87        let value = elem.as_value();
88
89        let Some(s) = value.as_number() else {
90            warnings.with_elem(WarningKind::InvalidType, elem);
91            return Err(warnings);
92        };
93
94        let mut decimal = match Decimal::from_str_exact(s) {
95            Ok(v) => v,
96            Err(err) => {
97                let kind = match err {
98                    rust_decimal::Error::ConversionTo(msg)
99                    | rust_decimal::Error::ErrorString(msg) => WarningKind::Internal(msg),
100                    rust_decimal::Error::ExceedsMaximumPossibleValue => {
101                        WarningKind::ExceedsMaximumPossibleValue
102                    }
103                    rust_decimal::Error::LessThanMinimumPossibleValue => {
104                        WarningKind::LessThanMinimumPossibleValue
105                    }
106                    rust_decimal::Error::Underflow => WarningKind::Underflow,
107                    rust_decimal::Error::ScaleExceedsMaximumPrecision(_) => {
108                        WarningKind::Internal(err.to_string())
109                    }
110                };
111
112                warnings.with_elem(kind, elem);
113                return Err(warnings);
114            }
115        };
116
117        decimal.rescale(DECIMAL_SCALE);
118        Ok(Self(decimal).into_caveat(warnings))
119    }
120}
121
122into_caveat!(Number);
123
124impl Number {
125    pub(crate) fn from_decimal(d: Decimal) -> Self {
126        Self(d)
127    }
128
129    pub fn is_zero(&self) -> bool {
130        self.0.is_zero()
131    }
132
133    #[must_use]
134    pub fn ceil(self) -> Self {
135        Self(self.0.ceil())
136    }
137
138    /// Round this number to the OCPI specified amount of decimals.
139    #[must_use]
140    pub fn rescale(mut self) -> Self {
141        self.0.rescale(DECIMAL_SCALE);
142        self
143    }
144
145    pub fn checked_div(self, other: Self) -> Option<Self> {
146        self.0.checked_div(other.0).map(Self)
147    }
148
149    #[must_use]
150    pub fn saturating_sub(self, other: Self) -> Self {
151        Self(self.0.saturating_sub(other.0))
152    }
153
154    #[must_use]
155    pub fn saturating_add(self, other: Self) -> Self {
156        Self(self.0.saturating_add(other.0))
157    }
158
159    #[must_use]
160    pub fn saturating_mul(self, other: Self) -> Self {
161        Self(self.0.saturating_mul(other.0))
162    }
163
164    #[must_use]
165    pub fn round_dp(self, digits: u32) -> Self {
166        Self(self.0.round_dp(digits))
167    }
168}
169
170impl From<Number> for Decimal {
171    fn from(value: Number) -> Self {
172        value.0
173    }
174}
175
176impl<'de> Deserialize<'de> for Number {
177    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
178    where
179        D: Deserializer<'de>,
180    {
181        let mut decimal = <Decimal as Deserialize>::deserialize(deserializer)?;
182        decimal.rescale(DECIMAL_SCALE);
183        Ok(Self(decimal))
184    }
185}
186
187impl Serialize for Number {
188    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189    where
190        S: Serializer,
191    {
192        let mut decimal = self.0;
193
194        decimal.rescale(DECIMAL_SCALE);
195        decimal.normalize_assign();
196
197        Serialize::serialize(&decimal, serializer)
198    }
199}
200
201impl From<Decimal> for Number {
202    fn from(value: Decimal) -> Self {
203        Self(value)
204    }
205}
206
207impl From<i64> for Number {
208    fn from(value: i64) -> Self {
209        Self(value.into())
210    }
211}
212
213impl From<u64> for Number {
214    fn from(value: u64) -> Self {
215        Self(value.into())
216    }
217}
218
219impl From<i32> for Number {
220    fn from(value: i32) -> Self {
221        Self(value.into())
222    }
223}
224
225impl TryFrom<Number> for i64 {
226    type Error = rust_decimal::Error;
227
228    fn try_from(value: Number) -> Result<Self, Self::Error> {
229        value.0.try_into()
230    }
231}
232
233impl fmt::Display for Number {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        self.0.fmt(f)
236    }
237}
238
239#[cfg(test)]
240mod test {
241    use rust_decimal::Decimal;
242
243    use crate::test::AsDecimal;
244
245    use super::Number;
246
247    impl AsDecimal for Number {
248        fn as_dec(&self) -> &Decimal {
249            &self.0
250        }
251    }
252}