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