Skip to content

Commit 7948574

Browse files
committed
supporting digits validation for normalized and non-normalized Decimal values
1 parent 866eb2d commit 7948574

File tree

1 file changed

+76
-53
lines changed

1 file changed

+76
-53
lines changed

src/validators/decimal.rs

Lines changed: 76 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,41 @@ impl_py_gc_traverse!(DecimalValidator {
8383
gt
8484
});
8585

86+
fn extract_decimal_digits_info<'data>(
87+
decimal: &PyAny,
88+
normalized: bool,
89+
py: Python<'data>,
90+
) -> ValResult<'data, (u64, u64)> {
91+
let mut normalized_decimal: Option<&PyAny> = None;
92+
if normalized {
93+
normalized_decimal = Some(decimal.call_method0(intern!(py, "normalize")).unwrap_or(decimal));
94+
}
95+
let (_, digit_tuple, exponent): (&PyAny, &PyTuple, &PyAny) = normalized_decimal
96+
.unwrap_or(decimal)
97+
.call_method0(intern!(py, "as_tuple"))?
98+
.extract()?;
99+
100+
// finite values have numeric exponent, we checked is_finite above
101+
let exponent: i64 = exponent.extract()?;
102+
let mut digits: u64 = u64::try_from(digit_tuple.len()).map_err(|e| ValError::InternalErr(e.into()))?;
103+
let decimals;
104+
if exponent >= 0 {
105+
// A positive exponent adds that many trailing zeros.
106+
digits += exponent as u64;
107+
decimals = 0;
108+
} else {
109+
// If the absolute value of the negative exponent is larger than the
110+
// number of digits, then it's the same as the number of digits,
111+
// because it'll consume all the digits in digit_tuple and then
112+
// add abs(exponent) - len(digit_tuple) leading zeros after the
113+
// decimal point.
114+
decimals = exponent.unsigned_abs();
115+
digits = digits.max(decimals);
116+
}
117+
118+
Ok((decimals, digits))
119+
}
120+
86121
impl Validator for DecimalValidator {
87122
fn validate<'data>(
88123
&self,
@@ -98,65 +133,53 @@ impl Validator for DecimalValidator {
98133
}
99134

100135
if self.check_digits {
101-
let normalized_value = decimal.call_method0(intern!(py, "normalize")).unwrap_or(decimal);
102-
let (_, digit_tuple, exponent): (&PyAny, &PyTuple, &PyAny) =
103-
normalized_value.call_method0(intern!(py, "as_tuple"))?.extract()?;
136+
if let Ok((normalized_decimals, normalized_digits)) = extract_decimal_digits_info(decimal, true, py) {
137+
if let Ok((decimals, digits)) = extract_decimal_digits_info(decimal, false, py) {
138+
if let Some(max_digits) = self.max_digits {
139+
if (digits > max_digits) & (normalized_digits > max_digits) {
140+
return Err(ValError::new(
141+
ErrorType::DecimalMaxDigits {
142+
max_digits,
143+
context: None,
144+
},
145+
input,
146+
));
147+
}
148+
}
104149

105-
// finite values have numeric exponent, we checked is_finite above
106-
let exponent: i64 = exponent.extract()?;
107-
let mut digits: u64 = u64::try_from(digit_tuple.len()).map_err(|e| ValError::InternalErr(e.into()))?;
108-
let decimals;
109-
if exponent >= 0 {
110-
// A positive exponent adds that many trailing zeros.
111-
digits += exponent as u64;
112-
decimals = 0;
113-
} else {
114-
// If the absolute value of the negative exponent is larger than the
115-
// number of digits, then it's the same as the number of digits,
116-
// because it'll consume all the digits in digit_tuple and then
117-
// add abs(exponent) - len(digit_tuple) leading zeros after the
118-
// decimal point.
119-
decimals = exponent.unsigned_abs();
120-
digits = digits.max(decimals);
121-
}
150+
if let Some(decimal_places) = self.decimal_places {
151+
if (decimals > decimal_places) & (normalized_decimals > decimal_places) {
152+
return Err(ValError::new(
153+
ErrorType::DecimalMaxPlaces {
154+
decimal_places,
155+
context: None,
156+
},
157+
input,
158+
));
159+
}
122160

123-
if let Some(max_digits) = self.max_digits {
124-
if digits > max_digits {
125-
return Err(ValError::new(
126-
ErrorType::DecimalMaxDigits {
127-
max_digits,
128-
context: None,
129-
},
130-
input,
131-
));
132-
}
133-
}
161+
if let Some(max_digits) = self.max_digits {
162+
let whole_digits = digits.saturating_sub(decimals);
163+
let max_whole_digits = max_digits.saturating_sub(decimal_places);
134164

135-
if let Some(decimal_places) = self.decimal_places {
136-
if decimals > decimal_places {
137-
return Err(ValError::new(
138-
ErrorType::DecimalMaxPlaces {
139-
decimal_places,
140-
context: None,
141-
},
142-
input,
143-
));
144-
}
165+
let normalized_whole_digits = normalized_digits.saturating_sub(normalized_decimals);
166+
let normalized_max_whole_digits = max_digits.saturating_sub(decimal_places);
145167

146-
if let Some(max_digits) = self.max_digits {
147-
let whole_digits = digits.saturating_sub(decimals);
148-
let max_whole_digits = max_digits.saturating_sub(decimal_places);
149-
if whole_digits > max_whole_digits {
150-
return Err(ValError::new(
151-
ErrorType::DecimalWholeDigits {
152-
whole_digits: max_whole_digits,
153-
context: None,
154-
},
155-
input,
156-
));
168+
if (whole_digits > max_whole_digits)
169+
& (normalized_whole_digits > normalized_max_whole_digits)
170+
{
171+
return Err(ValError::new(
172+
ErrorType::DecimalWholeDigits {
173+
whole_digits: max_whole_digits,
174+
context: None,
175+
},
176+
input,
177+
));
178+
}
179+
}
157180
}
158181
}
159-
}
182+
};
160183
}
161184
}
162185

0 commit comments

Comments
 (0)