1use std::{borrow::Cow, fmt};
6
7use rust_decimal::Decimal;
8
9use crate::{
10 into_caveat, json,
11 warning::{self, IntoCaveat},
12};
13
14pub const SCALE: u32 = 4;
18
19#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
21pub enum WarningKind {
22 ContainsEscapeCodes,
24
25 ExceedsMaximumPossibleValue,
27
28 InvalidType,
30
31 LessThanMinimumPossibleValue,
33
34 Underflow,
36}
37
38impl fmt::Display for WarningKind {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 WarningKind::ContainsEscapeCodes => write!(
42 f,
43 "The value contains escape codes but it does not need them"
44 ),
45 WarningKind::InvalidType => write!(f, "The value should be a number."),
46 WarningKind::ExceedsMaximumPossibleValue => {
47 write!(
48 f,
49 "The value provided exceeds `79,228,162,514,264,337,593,543,950,335`."
50 )
51 }
52 WarningKind::LessThanMinimumPossibleValue => write!(
53 f,
54 "The value provided is less than `-79,228,162,514,264,337,593,543,950,335`."
55 ),
56 WarningKind::Underflow => write!(
57 f,
58 "An underflow is when there are more than 28 fractional digits"
59 ),
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 }
73 }
74}
75
76into_caveat!(Decimal);
77
78impl json::FromJson<'_, '_> for Decimal {
79 type WarningKind = WarningKind;
80
81 fn from_json(elem: &json::Element<'_>) -> crate::Verdict<Self, Self::WarningKind> {
82 let mut warnings = warning::Set::new();
83 let value = elem.as_value();
84
85 let Some(s) = value.as_number() else {
86 warnings.with_elem(WarningKind::InvalidType, elem);
87 return Err(warnings);
88 };
89
90 let mut decimal = match Decimal::from_str_exact(s) {
91 Ok(v) => v,
92 Err(err) => {
93 let kind = match err {
94 rust_decimal::Error::ExceedsMaximumPossibleValue => {
95 WarningKind::ExceedsMaximumPossibleValue
96 }
97 rust_decimal::Error::LessThanMinimumPossibleValue => {
98 WarningKind::LessThanMinimumPossibleValue
99 }
100 rust_decimal::Error::Underflow => WarningKind::Underflow,
101 rust_decimal::Error::ConversionTo(_) => unreachable!("This is only triggered when converting to numerical types"),
102 rust_decimal::Error::ErrorString(_) => unreachable!("rust_decimal docs state: This is a legacy/deprecated error type retained for backwards compatibility."),
103 rust_decimal::Error::ScaleExceedsMaximumPrecision(_) => unreachable!("`Decimal::from_str_exact` uses a scale of zero")
104 };
105
106 warnings.with_elem(kind, elem);
107 return Err(warnings);
108 }
109 };
110
111 decimal.rescale(SCALE);
112 Ok(decimal.into_caveat(warnings))
113 }
114}
115
116pub(crate) fn deser_as_dec<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
120where
121 D: serde::Deserializer<'de>,
122{
123 use serde::Deserialize;
124
125 let mut d = <Decimal as Deserialize>::deserialize(deserializer)?;
126 d.rescale(SCALE);
127 Ok(d)
128}
129
130pub(crate) fn deser_as_opt_dec<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
132where
133 D: serde::Deserializer<'de>,
134{
135 use serde::Deserialize;
136
137 let d = Option::<Decimal>::deserialize(deserializer)?;
138 let d = d.map(|mut d| {
139 d.rescale(SCALE);
140 d
141 });
142 Ok(d)
143}
144
145pub(crate) fn ser_to_dec<S>(mut dec: Decimal, serializer: S) -> Result<S::Ok, S::Error>
147where
148 S: serde::Serializer,
149{
150 dec.rescale(SCALE);
151 dec.normalize_assign();
152
153 serde::Serialize::serialize(&dec, serializer)
154}
155
156#[doc(hidden)]
163#[macro_export]
164macro_rules! impl_dec_newtype {
165 ($kind:ident) => {
166 impl $kind {
167 #[allow(dead_code, reason = "auto generated")]
168 pub(crate) fn from_decimal(number: Decimal) -> Self {
169 Self(number)
170 }
171
172 #[must_use]
173 pub fn zero() -> Self {
174 use num_traits::Zero as _;
175
176 Self(Decimal::zero())
177 }
178
179 pub fn is_zero(&self) -> bool {
180 self.0.is_zero()
181 }
182
183 #[must_use]
185 pub fn rescale(mut self) -> Self {
186 self.0.rescale(number::SCALE);
187 Self(self.0)
188 }
189
190 #[must_use]
191 pub fn round_dp(self, digits: u32) -> Self {
192 Self(self.0.round_dp(digits))
193 }
194 }
195
196 impl std::fmt::Display for $kind {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 write!(f, "{:.4}", self.0)
199 }
200 }
201
202 impl From<$kind> for rust_decimal::Decimal {
205 fn from(value: $kind) -> Self {
206 value.0
207 }
208 }
209
210 #[cfg(test)]
211 impl From<u64> for $kind {
212 fn from(value: u64) -> Self {
213 Self(value.into())
214 }
215 }
216
217 #[cfg(test)]
218 impl From<rust_decimal::Decimal> for $kind {
219 fn from(value: rust_decimal::Decimal) -> Self {
220 Self(value)
221 }
222 }
223
224 impl $crate::SaturatingAdd for $kind {
225 fn saturating_add(self, other: Self) -> Self {
226 Self(self.0.saturating_add(other.0))
227 }
228 }
229
230 impl $crate::SaturatingSub for $kind {
231 fn saturating_sub(self, other: Self) -> Self {
232 Self(self.0.saturating_sub(other.0))
233 }
234 }
235
236 impl $crate::json::FromJson<'_, '_> for $kind {
237 type WarningKind = $crate::number::WarningKind;
238
239 fn from_json(elem: &json::Element<'_>) -> $crate::Verdict<Self, Self::WarningKind> {
240 rust_decimal::Decimal::from_json(elem).map(|v| v.map(Self))
241 }
242 }
243
244 impl<'de> serde::Deserialize<'de> for $kind {
245 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246 where
247 D: serde::Deserializer<'de>,
248 {
249 $crate::number::deser_as_dec(deserializer).map(Self)
250 }
251 }
252
253 impl serde::Serialize for $kind {
254 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
255 where
256 S: serde::Serializer,
257 {
258 $crate::number::ser_to_dec(self.0, serializer)
259 }
260 }
261
262 #[cfg(test)]
263 impl $crate::test::ApproxEq for $kind {
264 fn approx_eq(&self, other: &Self) -> bool {
265 const TOLERANCE: Decimal = rust_decimal_macros::dec!(0.1);
266 const PRECISION: u32 = 2;
267
268 $crate::test::approx_eq_dec(self.0, other.0, TOLERANCE, PRECISION)
269 }
270 }
271 };
272}
273
274#[cfg(test)]
275mod test {
276 use rust_decimal::Decimal;
277 use rust_decimal_macros::dec;
278
279 use crate::test::{approx_eq_dec, ApproxEq};
280
281 impl ApproxEq for Decimal {
282 fn approx_eq(&self, other: &Self) -> bool {
283 const TOLERANCE: Decimal = dec!(0.1);
284 const PRECISION: u32 = 2;
285
286 approx_eq_dec(*self, *other, TOLERANCE, PRECISION)
287 }
288 }
289}