1#[cfg(test)]
5pub mod test;
6
7#[cfg(test)]
8mod test_approx_eq;
9
10#[cfg(test)]
11mod test_round_to_ocpi;
12
13#[cfg(test)]
14mod test_parse_string;
15
16use std::{fmt, num::IntErrorKind};
17
18use rust_decimal::Decimal;
19
20use crate::{
21 json,
22 warning::{self, GatherWarnings, IntoCaveat},
23};
24
25pub const SCALE: u32 = 4;
29
30#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
32pub enum Warning {
33 ContainsEscapeCodes,
35
36 Decimal(String),
38
39 Decode(json::decode::Warning),
41
42 ExceedsMaximumPossibleValue,
44
45 ExcessivePrecision,
47
48 InvalidType { type_found: json::ValueKind },
50
51 LessThanMinimumPossibleValue,
53
54 Underflow,
56}
57
58impl Warning {
59 fn invalid_type(elem: &json::Element<'_>) -> Self {
60 Self::InvalidType {
61 type_found: elem.value().kind(),
62 }
63 }
64}
65
66impl From<json::decode::Warning> for Warning {
67 fn from(warning: json::decode::Warning) -> Self {
68 Self::Decode(warning)
69 }
70}
71
72impl fmt::Display for Warning {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 Self::ContainsEscapeCodes => f.write_str("The value contains escape codes but it does not need them"),
76 Self::Decimal(msg) => write!(f, "{msg}"),
77 Self::Decode(warning) => fmt::Display::fmt(warning, f),
78 Self::ExcessivePrecision => f.write_str("The number given has more than the four decimal precision required by the OCPI spec."),
79 Self::InvalidType { type_found } => {
80 write!(f, "The value should be a number but is `{type_found}`.")
81 }
82 Self::ExceedsMaximumPossibleValue => {
83 f.write_str("The value provided exceeds `79,228,162,514,264,337,593,543,950,335`.")
84 }
85 Self::LessThanMinimumPossibleValue => f.write_str("The value provided is less than `-79,228,162,514,264,337,593,543,950,335`."),
86 Self::Underflow => f.write_str("An underflow is when there are more than 28 fractional digits"),
87 }
88 }
89}
90
91impl crate::Warning for Warning {
92 fn id(&self) -> warning::Id {
93 match self {
94 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
95 Self::Decimal(_) => warning::Id::from_static("decimal"),
96 Self::Decode(warning) => warning.id(),
97 Self::ExcessivePrecision => warning::Id::from_static("excessive_precision"),
98 Self::InvalidType { .. } => warning::Id::from_static("invalid_type"),
99 Self::ExceedsMaximumPossibleValue => {
100 warning::Id::from_static("exceeds_maximum_possible_value")
101 }
102 Self::LessThanMinimumPossibleValue => {
103 warning::Id::from_static("less_than_minimum_possible_value")
104 }
105 Self::Underflow => warning::Id::from_static("underflow"),
106 }
107 }
108}
109
110pub(crate) fn int_error_kind_as_str(kind: IntErrorKind) -> &'static str {
111 match kind {
112 IntErrorKind::Empty => "empty",
113 IntErrorKind::InvalidDigit => "invalid digit",
114 IntErrorKind::PosOverflow => "positive overflow",
115 IntErrorKind::NegOverflow => "negative overflow",
116 IntErrorKind::Zero => "zero",
117 _ => "unknown",
118 }
119}
120
121impl json::FromJson<'_> for Decimal {
122 type Warning = Warning;
123
124 fn from_json(elem: &json::Element<'_>) -> crate::Verdict<Self, Self::Warning> {
125 let mut warnings = warning::Set::new();
126 let value = elem.as_value();
127
128 let s = if let Some(s) = value.as_number() {
130 s
131 } else {
132 let Some(raw_str) = value.to_raw_str() else {
135 return warnings.bail(Warning::invalid_type(elem), elem);
136 };
137
138 let pending_str = raw_str
139 .has_escapes(elem)
140 .gather_warnings_into(&mut warnings);
141
142 match pending_str {
143 json::decode::PendingStr::NoEscapes(s) => s,
144 json::decode::PendingStr::HasEscapes(_) => {
145 return warnings.bail(Warning::ContainsEscapeCodes, elem);
146 }
147 }
148 };
149
150 let mut decimal = match Decimal::from_str_exact(s) {
151 Ok(v) => v,
152 Err(err) => {
153 let kind = match err {
154 rust_decimal::Error::ExceedsMaximumPossibleValue => {
155 Warning::ExceedsMaximumPossibleValue
156 }
157 rust_decimal::Error::LessThanMinimumPossibleValue => {
158 Warning::LessThanMinimumPossibleValue
159 }
160 rust_decimal::Error::Underflow => Warning::Underflow,
161 rust_decimal::Error::ConversionTo(_) => {
162 unreachable!("This is only triggered when converting to numerical types")
163 }
164 rust_decimal::Error::ErrorString(msg) => Warning::Decimal(msg),
165 rust_decimal::Error::ScaleExceedsMaximumPrecision(_) => {
166 unreachable!("`Decimal::from_str_exact` uses a scale of zero")
167 }
168 };
169
170 return warnings.bail(kind, elem);
171 }
172 };
173
174 if decimal.scale() > SCALE {
175 warnings.insert(Warning::ExcessivePrecision, elem);
176 }
177
178 decimal.rescale(SCALE);
179 Ok(decimal.into_caveat(warnings))
180 }
181}
182
183pub(crate) trait FromDecimal {
184 fn from_decimal(d: Decimal) -> Self;
185}
186
187impl FromDecimal for Decimal {
191 fn from_decimal(mut d: Decimal) -> Self {
192 d.rescale(SCALE);
193 d
194 }
195}
196
197pub trait RoundDecimal {
201 #[must_use]
205 fn round_to_ocpi_scale(self) -> Self;
206}
207
208impl RoundDecimal for Decimal {
209 fn round_to_ocpi_scale(self) -> Self {
210 self.round_dp_with_strategy(SCALE, rust_decimal::RoundingStrategy::MidpointNearestEven)
211 }
212}
213
214impl<T: RoundDecimal> RoundDecimal for Option<T> {
215 fn round_to_ocpi_scale(self) -> Self {
216 self.map(RoundDecimal::round_to_ocpi_scale)
217 }
218}
219
220pub(crate) trait IsZero {
225 fn is_zero(&self) -> bool;
227}
228
229pub(crate) fn approx_eq_dec(a: &Decimal, b: &Decimal, tolerance: Decimal) -> bool {
231 let Some(diff) = a.checked_sub(*b) else {
234 return false;
235 };
236 diff.abs() <= tolerance
238}
239
240#[doc(hidden)]
247#[macro_export]
248macro_rules! impl_dec_newtype {
249 ($kind:ident, $unit:literal) => {
250 impl $kind {
251 #[must_use]
253 pub fn rescale(mut self) -> Self {
254 self.0.rescale(number::SCALE);
255 Self(self.0)
256 }
257
258 #[must_use]
259 pub fn round_dp(self, digits: u32) -> Self {
260 Self(self.0.round_dp(digits))
261 }
262 }
263
264 impl $crate::number::FromDecimal for $kind {
265 fn from_decimal(mut d: Decimal) -> Self {
266 d.rescale($crate::number::SCALE);
267 Self(d)
268 }
269 }
270
271 impl $crate::number::RoundDecimal for $kind {
272 fn round_to_ocpi_scale(self) -> Self {
273 Self(self.0.round_to_ocpi_scale())
274 }
275 }
276
277 impl std::fmt::Display for $kind {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 if self.0.is_zero() {
281 if f.alternate() {
282 write!(f, "0")
283 } else {
284 write!(f, "0{}", $unit)
285 }
286 } else {
287 if f.alternate() {
288 write!(f, "{:.4}", self.0)
289 } else {
290 write!(f, "{:.4}{}", self.0, $unit)
291 }
292 }
293 }
294 }
295
296 impl From<$kind> for rust_decimal::Decimal {
299 fn from(value: $kind) -> Self {
300 value.0
301 }
302 }
303
304 #[cfg(test)]
305 impl From<u64> for $kind {
306 fn from(value: u64) -> Self {
307 Self(value.into())
308 }
309 }
310
311 #[cfg(test)]
312 impl From<f64> for $kind {
313 fn from(value: f64) -> Self {
314 Self(Decimal::from_f64_retain(value).unwrap())
315 }
316 }
317
318 #[cfg(test)]
319 impl From<rust_decimal::Decimal> for $kind {
320 fn from(value: rust_decimal::Decimal) -> Self {
321 Self(value)
322 }
323 }
324
325 impl $crate::SaturatingAdd for $kind {
326 fn saturating_add(self, other: Self) -> Self {
327 Self(self.0.saturating_add(other.0))
328 }
329 }
330
331 impl $crate::SaturatingSub for $kind {
332 fn saturating_sub(self, other: Self) -> Self {
333 Self(self.0.saturating_sub(other.0))
334 }
335 }
336
337 impl $crate::json::FromJson<'_> for $kind {
338 type Warning = $crate::number::Warning;
339
340 fn from_json(elem: &json::Element<'_>) -> $crate::Verdict<Self, Self::Warning> {
341 rust_decimal::Decimal::from_json(elem).map(|v| v.map(Self))
342 }
343 }
344
345 #[cfg(test)]
346 impl $crate::test::ApproxEq for $kind {
347 type Tolerance = Decimal;
348
349 fn default_tolerance() -> Self::Tolerance {
350 rust_decimal_macros::dec!(0.1)
351 }
352
353 fn approx_eq_tolerance(&self, other: &Self, tolerance: Decimal) -> bool {
354 $crate::number::approx_eq_dec(&self.0, &other.0, tolerance)
355 }
356 }
357 };
358}