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
17#[cfg(test)]
18mod test_from_schema;
19
20use std::{fmt, num::IntErrorKind};
21
22use rust_decimal::Decimal;
23
24use crate::{
25 json, schema,
26 warning::{self, GatherWarnings as _, IntoCaveat as _},
27 FromSchema,
28};
29
30pub const SCALE: u32 = 4;
34
35#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
37pub enum Warning {
38 ContainsEscapeCodes,
40
41 Decimal(String),
43
44 Decode(json::decode::Warning),
46
47 ExceedsMaximumPossibleValue,
49
50 ExcessivePrecision,
52
53 InvalidType { type_found: json::ValueKind },
55
56 LessThanMinimumPossibleValue,
58
59 Underflow,
61}
62
63impl Warning {
64 fn invalid_type(elem: &json::Element<'_>) -> Self {
65 Self::InvalidType {
66 type_found: elem.value().kind(),
67 }
68 }
69}
70
71impl From<json::decode::Warning> for Warning {
72 fn from(warning: json::decode::Warning) -> Self {
73 Self::Decode(warning)
74 }
75}
76
77impl fmt::Display for Warning {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 Self::ContainsEscapeCodes => f.write_str("The value contains escape codes but it does not need them"),
81 Self::Decimal(msg) => write!(f, "{msg}"),
82 Self::Decode(warning) => fmt::Display::fmt(warning, f),
83 Self::ExcessivePrecision => f.write_str("The number given has more than the four decimal precision required by the OCPI spec."),
84 Self::InvalidType { type_found } => {
85 write!(f, "The value should be a number but is `{type_found}`.")
86 }
87 Self::ExceedsMaximumPossibleValue => {
88 f.write_str("The value provided exceeds `79,228,162,514,264,337,593,543,950,335`.")
89 }
90 Self::LessThanMinimumPossibleValue => f.write_str("The value provided is less than `-79,228,162,514,264,337,593,543,950,335`."),
91 Self::Underflow => f.write_str("An underflow is when there are more than 28 fractional digits"),
92 }
93 }
94}
95
96impl crate::Warning for Warning {
97 fn id(&self) -> warning::Id {
98 match self {
99 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
100 Self::Decimal(_) => warning::Id::from_static("decimal"),
101 Self::Decode(warning) => warning.id(),
102 Self::ExcessivePrecision => warning::Id::from_static("excessive_precision"),
103 Self::InvalidType { type_found } => {
104 warning::Id::from_string(format!("invalid_type({type_found})"))
105 }
106 Self::ExceedsMaximumPossibleValue => {
107 warning::Id::from_static("exceeds_maximum_possible_value")
108 }
109 Self::LessThanMinimumPossibleValue => {
110 warning::Id::from_static("less_than_minimum_possible_value")
111 }
112 Self::Underflow => warning::Id::from_static("underflow"),
113 }
114 }
115}
116
117pub(crate) fn int_error_kind_as_str(kind: IntErrorKind) -> &'static str {
118 match kind {
119 IntErrorKind::Empty => "empty",
120 IntErrorKind::InvalidDigit => "invalid digit",
121 IntErrorKind::PosOverflow => "positive overflow",
122 IntErrorKind::NegOverflow => "negative overflow",
123 IntErrorKind::Zero => "zero",
124 _ => "unknown",
125 }
126}
127
128impl<'buf> FromSchema<'buf, schema::Number<'buf>> for Decimal {
129 type Warning = Warning;
130
131 fn from_schema(source: &schema::Number<'buf>) -> crate::Verdict<Self, Self::Warning> {
132 let mut warnings = warning::Set::new();
133
134 let (elem, digits) = match source {
138 schema::Number::Number { elem, digits } => (elem, *digits),
139 schema::Number::StringEncoded { elem, value } => {
140 let pending_str = value.has_escapes(elem).gather_warnings_into(&mut warnings);
141
142 match pending_str {
143 json::PendingStr::NoEscapes(s) => (elem, s),
144 json::PendingStr::HasEscapes(_) => {
145 return warnings.bail(elem, Warning::ContainsEscapeCodes);
146 }
147 }
148 }
149 };
150
151 let decimal = match Decimal::from_str_exact(digits) {
152 Ok(v) => v,
153 Err(err) => {
154 let kind = match err {
155 rust_decimal::Error::ExceedsMaximumPossibleValue => {
156 Warning::ExceedsMaximumPossibleValue
157 }
158 rust_decimal::Error::LessThanMinimumPossibleValue => {
159 Warning::LessThanMinimumPossibleValue
160 }
161 rust_decimal::Error::Underflow => Warning::Underflow,
162 rust_decimal::Error::ConversionTo(_) => {
163 unreachable!("This is only triggered when converting to numerical types")
164 }
165 rust_decimal::Error::ErrorString(msg) => Warning::Decimal(msg),
166 rust_decimal::Error::ScaleExceedsMaximumPrecision(_) => {
167 unreachable!("`Decimal::from_str_exact` uses a scale of zero")
168 }
169 };
170
171 return warnings.bail(elem, kind);
172 }
173 };
174
175 if decimal.scale() > SCALE {
176 warnings.insert(elem, Warning::ExcessivePrecision);
177 }
178
179 Ok(decimal.into_caveat(warnings))
180 }
181}
182
183impl json::FromJson<'_> for Decimal {
184 type Warning = Warning;
185
186 fn from_json(elem: &json::Element<'_>) -> crate::Verdict<Self, Self::Warning> {
187 let mut warnings = warning::Set::new();
188 let value = elem.as_value();
189
190 let s = if let Some(s) = value.as_number() {
192 s
193 } else {
194 let Some(raw_str) = value.to_raw_str() else {
197 return warnings.bail(elem, Warning::invalid_type(elem));
198 };
199
200 let pending_str = raw_str
201 .has_escapes(elem)
202 .gather_warnings_into(&mut warnings);
203
204 match pending_str {
205 json::PendingStr::NoEscapes(s) => s,
206 json::PendingStr::HasEscapes(_) => {
207 return warnings.bail(elem, Warning::ContainsEscapeCodes);
208 }
209 }
210 };
211
212 let decimal = match Decimal::from_str_exact(s) {
213 Ok(v) => v,
214 Err(err) => {
215 let kind = match err {
216 rust_decimal::Error::ExceedsMaximumPossibleValue => {
217 Warning::ExceedsMaximumPossibleValue
218 }
219 rust_decimal::Error::LessThanMinimumPossibleValue => {
220 Warning::LessThanMinimumPossibleValue
221 }
222 rust_decimal::Error::Underflow => Warning::Underflow,
223 rust_decimal::Error::ConversionTo(_) => {
224 unreachable!("This is only triggered when converting to numerical types")
225 }
226 rust_decimal::Error::ErrorString(msg) => Warning::Decimal(msg),
227 rust_decimal::Error::ScaleExceedsMaximumPrecision(_) => {
228 unreachable!("`Decimal::from_str_exact` uses a scale of zero")
229 }
230 };
231
232 return warnings.bail(elem, kind);
233 }
234 };
235
236 if decimal.scale() > SCALE {
237 warnings.insert(elem, Warning::ExcessivePrecision);
238 }
239
240 Ok(decimal.into_caveat(warnings))
241 }
242}
243
244pub(crate) trait FromDecimal {
245 fn from_decimal(d: Decimal) -> Self;
246}
247
248impl FromDecimal for Decimal {
252 fn from_decimal(mut d: Decimal) -> Self {
253 d.rescale(SCALE);
254 d
255 }
256}
257
258pub trait RoundDecimal {
262 #[must_use]
266 fn round_to_ocpi_scale(self) -> Self;
267}
268
269impl RoundDecimal for Decimal {
270 fn round_to_ocpi_scale(self) -> Self {
271 self.round_dp_with_strategy(SCALE, rust_decimal::RoundingStrategy::MidpointNearestEven)
272 }
273}
274
275impl<T: RoundDecimal> RoundDecimal for Option<T> {
276 fn round_to_ocpi_scale(self) -> Self {
277 self.map(RoundDecimal::round_to_ocpi_scale)
278 }
279}
280
281pub(crate) trait IsZero {
286 fn is_zero(&self) -> bool;
288}
289
290pub(crate) fn approx_eq_dec(a: &Decimal, b: &Decimal, tolerance: Decimal) -> bool {
292 let Some(diff) = a.checked_sub(*b) else {
295 return false;
296 };
297 diff.abs() <= tolerance
299}
300
301#[doc(hidden)]
308#[macro_export]
309macro_rules! impl_dec_newtype {
310 ($kind:ident, $unit:literal) => {
311 impl $kind {
312 #[must_use]
314 pub fn rescale(mut self) -> Self {
315 self.0.rescale(number::SCALE);
316 Self(self.0)
317 }
318
319 #[must_use]
320 pub fn round_dp(self, digits: u32) -> Self {
321 Self(self.0.round_dp(digits))
322 }
323 }
324
325 impl $crate::number::FromDecimal for $kind {
326 fn from_decimal(d: Decimal) -> Self {
327 Self(d)
328 }
329 }
330
331 impl $crate::number::RoundDecimal for $kind {
332 fn round_to_ocpi_scale(self) -> Self {
333 Self(self.0.round_to_ocpi_scale())
334 }
335 }
336
337 impl std::fmt::Display for $kind {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 if self.0.is_zero() {
341 if f.alternate() {
342 write!(f, "0")
343 } else {
344 write!(f, "0{}", $unit)
345 }
346 } else {
347 if f.alternate() {
348 write!(f, "{:.4}", self.0)
349 } else {
350 write!(f, "{:.4}{}", self.0, $unit)
351 }
352 }
353 }
354 }
355
356 impl From<$kind> for rust_decimal::Decimal {
359 fn from(value: $kind) -> Self {
360 value.0
361 }
362 }
363
364 #[cfg(test)]
365 impl From<u64> for $kind {
366 fn from(value: u64) -> Self {
367 Self(value.into())
368 }
369 }
370
371 #[cfg(test)]
372 impl From<f64> for $kind {
373 fn from(value: f64) -> Self {
374 Self(Decimal::from_f64_retain(value).unwrap())
375 }
376 }
377
378 #[cfg(test)]
379 impl From<rust_decimal::Decimal> for $kind {
380 fn from(value: rust_decimal::Decimal) -> Self {
381 Self(value)
382 }
383 }
384
385 impl $crate::SaturatingAdd for $kind {
386 fn saturating_add(self, other: Self) -> Self {
387 Self(self.0.saturating_add(other.0))
388 }
389 }
390
391 impl $crate::SaturatingSub for $kind {
392 fn saturating_sub(self, other: Self) -> Self {
393 Self(self.0.saturating_sub(other.0))
394 }
395 }
396
397 impl $crate::json::FromJson<'_> for $kind {
398 type Warning = $crate::number::Warning;
399
400 fn from_json(elem: &json::Element<'_>) -> $crate::Verdict<Self, Self::Warning> {
401 rust_decimal::Decimal::from_json(elem).map(|v| v.map(Self))
402 }
403 }
404
405 impl<'buf> $crate::FromSchema<'buf, $crate::schema::Number<'buf>> for $kind {
406 type Warning = $crate::number::Warning;
407
408 fn from_schema(
409 source: &$crate::schema::Number<'buf>,
410 ) -> $crate::Verdict<Self, Self::Warning> {
411 let decimal: $crate::Verdict<rust_decimal::Decimal, $crate::number::Warning> =
412 $crate::FromSchema::from_schema(source);
413 decimal.map(|v| v.map(Self))
414 }
415 }
416
417 #[cfg(test)]
418 impl $crate::test::ApproxEq for $kind {
419 type Tolerance = Decimal;
420
421 fn default_tolerance() -> Self::Tolerance {
422 rust_decimal_macros::dec!(0.1)
423 }
424
425 fn approx_eq_tolerance(&self, other: &Self, tolerance: Decimal) -> bool {
426 $crate::number::approx_eq_dec(&self.0, &other.0, tolerance)
427 }
428 }
429 };
430}