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