Skip to content

Commit 42a465a

Browse files
authored
prevent int,float,decimal -> str (#212)
* prevent int,float,decimal -> str, fix #150 * fix tests * coverage
1 parent 1e93886 commit 42a465a

File tree

11 files changed

+64
-60
lines changed

11 files changed

+64
-60
lines changed

src/input/input_json.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ impl<'a> Input<'a> for JsonInput {
6262
fn lax_str(&'a self) -> ValResult<EitherString<'a>> {
6363
match self {
6464
JsonInput::String(s) => Ok(s.as_str().into()),
65-
JsonInput::Int(int) => Ok(int.to_string().into()),
66-
JsonInput::Float(float) => Ok(float.to_string().into()),
6765
_ => Err(ValError::new(ErrorKind::StrType, self)),
6866
}
6967
}

src/input/input_python.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use std::str::from_utf8;
44
use pyo3::exceptions::PyAttributeError;
55
use pyo3::prelude::*;
66
use pyo3::types::{
7-
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFrozenSet, PyInt, PyList, PyMapping,
8-
PySequence, PySet, PyString, PyTime, PyTuple, PyType,
7+
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFrozenSet, PyList, PyMapping, PySequence,
8+
PySet, PyString, PyTime, PyTuple, PyType,
99
};
1010
use pyo3::{intern, AsPyPointer};
1111

@@ -113,16 +113,6 @@ impl<'a> Input<'a> for PyAny {
113113
Err(_) => return Err(ValError::new(ErrorKind::StrUnicode, self)),
114114
};
115115
Ok(str.into())
116-
} else if self.cast_as::<PyBool>().is_ok() {
117-
// do this before int and float parsing as `False` is cast to `0` and we don't want False to
118-
// be returned as a string
119-
Err(ValError::new(ErrorKind::StrType, self))
120-
} else if let Ok(int) = self.cast_as::<PyInt>() {
121-
let int = i64::extract(int)?;
122-
Ok(int.to_string().into())
123-
} else if let Ok(float) = f64::extract(self) {
124-
// don't cast_as here so Decimals are covered - internally f64:extract uses PyFloat_AsDouble
125-
Ok(float.to_string().into())
126116
} else {
127117
Err(ValError::new(ErrorKind::StrType, self))
128118
}

src/input/return_enums.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,6 @@ impl<'a> EitherString<'a> {
227227
}
228228
}
229229

230-
impl<'a> From<String> for EitherString<'a> {
231-
fn from(data: String) -> Self {
232-
Self::Cow(Cow::Owned(data))
233-
}
234-
}
235-
236230
impl<'a> From<&'a str> for EitherString<'a> {
237231
fn from(data: &'a str) -> Self {
238232
Self::Cow(Cow::Borrowed(data))

tests/benchmarks/test_complete_benchmark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def test_complete_invalid():
9191
lax_validator = SchemaValidator(lax_schema)
9292
with pytest.raises(ValidationError) as exc_info:
9393
lax_validator.validate_python(input_data_wrong())
94-
assert len(exc_info.value.errors()) == 736
94+
assert len(exc_info.value.errors()) == 738
9595

9696
model = pydantic_model()
9797
if model is None:

tests/benchmarks/test_micro_benchmarks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def test_frozenset_of_ints_core(benchmark):
415415
benchmark(v.validate_python, frozenset_of_ints)
416416

417417

418-
dict_of_ints_data = ({i: i for i in range(1000)}, {i: str(i) for i in range(1000)})
418+
dict_of_ints_data = ({str(i): i for i in range(1000)}, {str(i): str(i) for i in range(1000)})
419419

420420

421421
@skip_pydantic

tests/test_json.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ def test_null():
2929

3030

3131
def test_str():
32-
assert SchemaValidator({'type': 'str'}).validate_json('"foobar"') == 'foobar'
33-
assert SchemaValidator({'type': 'str'}).validate_json('123') == '123'
32+
s = SchemaValidator({'type': 'str'})
33+
assert s.validate_json('"foobar"') == 'foobar'
3434
with pytest.raises(ValidationError, match=r'Input should be a valid string \[kind=str_type,'):
35-
SchemaValidator({'type': 'str'}).validate_json('false')
35+
s.validate_json('false')
36+
with pytest.raises(ValidationError, match=r'Input should be a valid string \[kind=str_type,'):
37+
s.validate_json('123')
3638

3739

3840
@pytest.mark.parametrize(
@@ -53,8 +55,8 @@ def test_model():
5355
)
5456

5557
# language=json
56-
input_str = '{"field_a": 123, "field_b": 1}'
57-
assert v.validate_json(input_str) == {'field_a': '123', 'field_b': 1}
58+
input_str = '{"field_a": "abc", "field_b": 1}'
59+
assert v.validate_json(input_str) == {'field_a': 'abc', 'field_b': 1}
5860

5961

6062
def test_float_no_remainder():

tests/validators/test_dict.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ def test_dict(py_and_json: PyAndJson):
2424
@pytest.mark.parametrize(
2525
'input_value,expected',
2626
[
27-
({'1': 1, '2': 2}, {'1': '1', '2': '2'}),
28-
(OrderedDict(a=1, b=2), {'a': '1', 'b': '2'}),
27+
({'1': b'1', '2': b'2'}, {'1': '1', '2': '2'}),
28+
(OrderedDict(a=b'1', b='2'), {'a': '1', 'b': '2'}),
2929
({}, {}),
3030
('foobar', Err("Input should be a valid dictionary [kind=dict_type, input_value='foobar', input_type=str]")),
3131
([], Err('Input should be a valid dictionary [kind=dict_type,')),

tests/validators/test_function.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def f(input_value, **kwargs):
168168
}
169169
)
170170

171-
assert v.validate_python({'field_a': '123', 'field_b': 321}) == {'field_a': 123, 'field_b': '321 Changed'}
171+
assert v.validate_python({'field_a': '123', 'field_b': b'321'}) == {'field_a': 123, 'field_b': '321 Changed'}
172172
assert f_kwargs == {'data': {'field_a': 123}, 'config': None, 'context': None}
173173

174174

@@ -192,7 +192,7 @@ def f(input_value, **kwargs):
192192
{'config_choose_priority': 2},
193193
)
194194

195-
assert v.validate_python({'test_field': 321}) == {'test_field': '321 Changed'}
195+
assert v.validate_python({'test_field': b'321'}) == {'test_field': '321 Changed'}
196196
assert f_kwargs == {'data': {}, 'config': {'config_choose_priority': 2}, 'context': None}
197197

198198

@@ -206,7 +206,7 @@ def f(input_value, **kwargs):
206206

207207
v = SchemaValidator({'type': 'function', 'mode': 'after', 'function': f, 'schema': {'type': 'str'}})
208208

209-
assert v.validate_python(123) == '123 Changed'
209+
assert v.validate_python(b'abc') == 'abc Changed'
210210
assert f_kwargs == {'data': None, 'config': None, 'context': None}
211211

212212

@@ -241,7 +241,7 @@ def f(input_value, **kwargs):
241241

242242
m = {'field_a': 'test', 'more': 'foobar'}
243243
assert v.validate_python({'field_a': 'test'}) == m
244-
assert v.validate_assignment('field_a', 456, m) == {'field_a': '456', 'more': 'foobar'}
244+
assert v.validate_assignment('field_a', b'abc', m) == {'field_a': 'abc', 'more': 'foobar'}
245245

246246

247247
def test_function_wrong_sig():
@@ -279,9 +279,9 @@ def __validate__(cls, input_value, **kwargs):
279279
assert isinstance(f, Foobar)
280280
assert f.a == 'foofoo'
281281

282-
f = v.validate_python(1)
282+
f = v.validate_python(b'a')
283283
assert isinstance(f, Foobar)
284-
assert f.a == '11'
284+
assert f.a == 'aa'
285285

286286
with pytest.raises(ValidationError) as exc_info:
287287
v.validate_python(True)

tests/validators/test_string.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
'input_value,expected',
1414
[
1515
('foobar', 'foobar'),
16-
(123, '123'),
17-
(123.456, '123.456'),
16+
(123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
17+
(123.456, Err('Input should be a valid string [kind=str_type, input_value=123.456, input_type=float]')),
1818
(False, Err('Input should be a valid string [kind=str_type')),
1919
(True, Err('Input should be a valid string [kind=str_type')),
2020
([], Err('Input should be a valid string [kind=str_type, input_value=[], input_type=list]')),
@@ -46,8 +46,11 @@ def test_str(py_and_json: PyAndJson, input_value, expected):
4646
),
4747
# null bytes are very annoying, but we can't really block them here
4848
(b'\x00', '\x00'),
49-
(123, '123'),
50-
(Decimal('123'), '123'),
49+
(123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
50+
(
51+
Decimal('123'),
52+
Err("Input should be a valid string [kind=str_type, input_value=Decimal('123'), input_type=Decimal]"),
53+
),
5154
],
5255
)
5356
def test_str_not_json(input_value, expected):
@@ -62,9 +65,8 @@ def test_str_not_json(input_value, expected):
6265
@pytest.mark.parametrize(
6366
'kwargs,input_value,expected',
6467
[
65-
({}, 123, '123'),
68+
({}, 'abc', 'abc'),
6669
({'strict': True}, 'Foobar', 'Foobar'),
67-
({'strict': True}, 123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
6870
({'to_upper': True}, 'fooBar', 'FOOBAR'),
6971
({'to_lower': True}, 'fooBar', 'foobar'),
7072
({'strip_whitespace': True}, ' foobar ', 'foobar'),
@@ -93,6 +95,23 @@ def test_constrained_str(py_and_json: PyAndJson, kwargs: Dict[str, Any], input_v
9395
assert v.validate_test(input_value) == expected
9496

9597

98+
@pytest.mark.parametrize(
99+
'kwargs,input_value,expected',
100+
[
101+
({}, b'abc', 'abc'),
102+
({'strict': True}, 'Foobar', 'Foobar'),
103+
({'strict': True}, 123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
104+
],
105+
)
106+
def test_constrained_str_py_only(kwargs: Dict[str, Any], input_value, expected):
107+
v = SchemaValidator({'type': 'str', **kwargs})
108+
if isinstance(expected, Err):
109+
with pytest.raises(ValidationError, match=re.escape(expected.message)):
110+
v.validate_python(input_value)
111+
else:
112+
assert v.validate_python(input_value) == expected
113+
114+
96115
def test_unicode_error():
97116
# `.to_str()` Returns a `UnicodeEncodeError` if the input is not valid unicode (containing unpaired surrogates).
98117
# https://github.com/PyO3/pyo3/blob/6503128442b8f3e767c663a6a8d96376d7fb603d/src/types/string.rs#L477

tests/validators/test_tuple.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def test_any_no_copy():
5151
'mode,items,input_value,expected',
5252
[
5353
('variable', {'type': 'int'}, (1, 2, '33'), (1, 2, 33)),
54-
('variable', {'type': 'str'}, (1, 2, '33'), ('1', '2', '33')),
55-
('positional', [{'type': 'int'}, {'type': 'str'}, {'type': 'float'}], (1, 2, 33), (1, '2', 33.0)),
54+
('variable', {'type': 'str'}, (b'1', b'2', '33'), ('1', '2', '33')),
55+
('positional', [{'type': 'int'}, {'type': 'str'}, {'type': 'float'}], (1, b'a', 33), (1, 'a', 33.0)),
5656
],
5757
)
5858
def test_tuple_strict_passes_with_tuple(mode, items, input_value, expected):
@@ -227,7 +227,7 @@ def test_union_tuple_list(input_value, expected):
227227
[
228228
((1, 2, 3), (1, 2, 3)),
229229
(('a', 'b', 'c'), ('a', 'b', 'c')),
230-
(('a', 1, 'c'), ('a', '1', 'c')),
230+
(('a', b'a', 'c'), ('a', 'a', 'c')),
231231
(
232232
[5],
233233
Err(
@@ -251,6 +251,7 @@ def test_union_tuple_list(input_value, expected):
251251
),
252252
),
253253
],
254+
ids=repr,
254255
)
255256
def test_union_tuple_var_len(input_value, expected):
256257
v = SchemaValidator(
@@ -276,7 +277,6 @@ def test_union_tuple_var_len(input_value, expected):
276277
[
277278
((1, 2, 3), (1, 2, 3)),
278279
(('a', 'b', 'c'), ('a', 'b', 'c')),
279-
(('a', 1, 'c'), ('a', '1', 'c')),
280280
(
281281
[5, '1', 1],
282282
Err(
@@ -298,6 +298,7 @@ def test_union_tuple_var_len(input_value, expected):
298298
),
299299
),
300300
],
301+
ids=repr,
301302
)
302303
def test_union_tuple_fix_len(input_value, expected):
303304
v = SchemaValidator(
@@ -349,10 +350,10 @@ def test_tuple_fix_extra():
349350

350351
def test_tuple_fix_extra_any():
351352
v = SchemaValidator({'type': 'tuple', 'mode': 'positional', 'items_schema': ['str'], 'extra_schema': 'any'})
352-
assert v.validate_python([1]) == ('1',)
353-
assert v.validate_python([1, 2]) == ('1', 2)
354-
assert v.validate_python((1, 2)) == ('1', 2)
355-
assert v.validate_python([1, 2, b'3']) == ('1', 2, b'3')
353+
assert v.validate_python([b'1']) == ('1',)
354+
assert v.validate_python([b'1', 2]) == ('1', 2)
355+
assert v.validate_python((b'1', 2)) == ('1', 2)
356+
assert v.validate_python([b'1', 2, b'3']) == ('1', 2, b'3')
356357
with pytest.raises(ValidationError) as exc_info:
357358
v.validate_python([])
358359
assert exc_info.value.errors() == [{'kind': 'missing', 'loc': [0], 'message': 'Field required', 'input_value': []}]

0 commit comments

Comments
 (0)