Skip to content

Commit 5c90851

Browse files
authored
Add ultra-strict mode for better union decisions (#536)
* make strict float not allow an int * adding different_strict_behavior() * revert float test changes * add tests * improve testing structure * support definition references, test * update validate_float
1 parent dd98ccc commit 5c90851

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+668
-21
lines changed

src/input/input_abstract.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,16 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
129129
self.strict_int()
130130
}
131131

132-
fn validate_float(&self, strict: bool) -> ValResult<f64> {
133-
if strict {
132+
fn validate_float(&self, strict: bool, ultra_strict: bool) -> ValResult<f64> {
133+
if ultra_strict {
134+
self.ultra_strict_float()
135+
} else if strict {
134136
self.strict_float()
135137
} else {
136138
self.lax_float()
137139
}
138140
}
141+
fn ultra_strict_float(&self) -> ValResult<f64>;
139142
fn strict_float(&self) -> ValResult<f64>;
140143
#[cfg_attr(has_no_coverage, no_coverage)]
141144
fn lax_float(&self) -> ValResult<f64> {

src/input/input_json.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ impl<'a> Input<'a> for JsonInput {
141141
}
142142
}
143143

144+
fn ultra_strict_float(&self) -> ValResult<f64> {
145+
match self {
146+
JsonInput::Float(f) => Ok(*f),
147+
_ => Err(ValError::new(ErrorType::FloatType, self)),
148+
}
149+
}
144150
fn strict_float(&self) -> ValResult<f64> {
145151
match self {
146152
JsonInput::Float(f) => Ok(*f),
@@ -368,6 +374,10 @@ impl<'a> Input<'a> for String {
368374
}
369375
}
370376

377+
#[cfg_attr(has_no_coverage, no_coverage)]
378+
fn ultra_strict_float(&self) -> ValResult<f64> {
379+
self.strict_float()
380+
}
371381
#[cfg_attr(has_no_coverage, no_coverage)]
372382
fn strict_float(&self) -> ValResult<f64> {
373383
Err(ValError::new(ErrorType::FloatType, self))

src/input/input_python.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use std::str::from_utf8;
44
use pyo3::once_cell::GILOnceCell;
55
use pyo3::prelude::*;
66
use pyo3::types::{
7-
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFrozenSet, PyIterator, PyList, PyMapping,
8-
PySet, PyString, PyTime, PyTuple, PyType,
7+
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFrozenSet, PyInt, PyIterator, PyList,
8+
PyMapping, PySet, PyString, PyTime, PyTuple, PyType,
99
};
1010
#[cfg(not(PyPy))]
1111
use pyo3::types::{PyDictItems, PyDictKeys, PyDictValues};
@@ -289,6 +289,15 @@ impl<'a> Input<'a> for PyAny {
289289
}
290290
}
291291

292+
fn ultra_strict_float(&self) -> ValResult<f64> {
293+
if matches!(self.is_instance_of::<PyInt>(), Ok(true)) {
294+
Err(ValError::new(ErrorType::FloatType, self))
295+
} else if let Ok(float) = self.extract::<f64>() {
296+
Ok(float)
297+
} else {
298+
Err(ValError::new(ErrorType::FloatType, self))
299+
}
300+
}
292301
fn strict_float(&self) -> ValResult<f64> {
293302
if self.extract::<bool>().is_ok() {
294303
Err(ValError::new(ErrorType::FloatType, self))
@@ -298,7 +307,6 @@ impl<'a> Input<'a> for PyAny {
298307
Err(ValError::new(ErrorType::FloatType, self))
299308
}
300309
}
301-
302310
fn lax_float(&self) -> ValResult<f64> {
303311
if let Ok(float) = self.extract::<f64>() {
304312
Ok(float)

src/validators/any.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ impl Validator for AnyValidator {
3636
Ok(input.to_object(py))
3737
}
3838

39+
fn different_strict_behavior(
40+
&self,
41+
_build_context: Option<&BuildContext<CombinedValidator>>,
42+
_ultra_strict: bool,
43+
) -> bool {
44+
false
45+
}
46+
3947
fn get_name(&self) -> &str {
4048
Self::EXPECTED_TYPE
4149
}

src/validators/arguments.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,16 @@ impl Validator for ArgumentsValidator {
326326
}
327327
}
328328

329+
fn different_strict_behavior(
330+
&self,
331+
build_context: Option<&BuildContext<CombinedValidator>>,
332+
ultra_strict: bool,
333+
) -> bool {
334+
self.parameters
335+
.iter()
336+
.any(|p| p.validator.different_strict_behavior(build_context, ultra_strict))
337+
}
338+
329339
fn get_name(&self) -> &str {
330340
Self::EXPECTED_TYPE
331341
}

src/validators/bool.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ impl Validator for BoolValidator {
4242
Ok(input.validate_bool(extra.strict.unwrap_or(self.strict))?.into_py(py))
4343
}
4444

45+
fn different_strict_behavior(
46+
&self,
47+
_build_context: Option<&BuildContext<CombinedValidator>>,
48+
ultra_strict: bool,
49+
) -> bool {
50+
!ultra_strict
51+
}
52+
4553
fn get_name(&self) -> &str {
4654
Self::EXPECTED_TYPE
4755
}

src/validators/bytes.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ impl Validator for BytesValidator {
4949
Ok(either_bytes.into_py(py))
5050
}
5151

52+
fn different_strict_behavior(
53+
&self,
54+
_build_context: Option<&BuildContext<CombinedValidator>>,
55+
ultra_strict: bool,
56+
) -> bool {
57+
!ultra_strict
58+
}
59+
5260
fn get_name(&self) -> &str {
5361
Self::EXPECTED_TYPE
5462
}
@@ -91,6 +99,14 @@ impl Validator for BytesConstrainedValidator {
9199
Ok(either_bytes.into_py(py))
92100
}
93101

102+
fn different_strict_behavior(
103+
&self,
104+
_build_context: Option<&BuildContext<CombinedValidator>>,
105+
ultra_strict: bool,
106+
) -> bool {
107+
!ultra_strict
108+
}
109+
94110
fn get_name(&self) -> &str {
95111
"constrained-bytes"
96112
}

src/validators/call.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ impl Validator for CallValidator {
8181
}
8282
}
8383

84+
fn different_strict_behavior(
85+
&self,
86+
build_context: Option<&BuildContext<CombinedValidator>>,
87+
ultra_strict: bool,
88+
) -> bool {
89+
if let Some(return_validator) = &self.return_validator {
90+
if return_validator.different_strict_behavior(build_context, ultra_strict) {
91+
return true;
92+
}
93+
}
94+
self.arguments_validator
95+
.different_strict_behavior(build_context, ultra_strict)
96+
}
97+
8498
fn get_name(&self) -> &str {
8599
&self.name
86100
}

src/validators/callable.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ impl Validator for CallableValidator {
3737
}
3838
}
3939

40+
fn different_strict_behavior(
41+
&self,
42+
_build_context: Option<&BuildContext<CombinedValidator>>,
43+
_ultra_strict: bool,
44+
) -> bool {
45+
false
46+
}
47+
4048
fn get_name(&self) -> &str {
4149
Self::EXPECTED_TYPE
4250
}

src/validators/chain.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ impl Validator for ChainValidator {
8585
})
8686
}
8787

88+
fn different_strict_behavior(
89+
&self,
90+
build_context: Option<&BuildContext<CombinedValidator>>,
91+
ultra_strict: bool,
92+
) -> bool {
93+
self.steps
94+
.iter()
95+
.any(|v| v.different_strict_behavior(build_context, ultra_strict))
96+
}
97+
8898
fn get_name(&self) -> &str {
8999
&self.name
90100
}

0 commit comments

Comments
 (0)