Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prevent int,float,decimal -> str #212

Merged
merged 3 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/input/input_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ impl<'a> Input<'a> for JsonInput {
fn lax_str(&'a self) -> ValResult<EitherString<'a>> {
match self {
JsonInput::String(s) => Ok(s.as_str().into()),
JsonInput::Int(int) => Ok(int.to_string().into()),
JsonInput::Float(float) => Ok(float.to_string().into()),
_ => Err(ValError::new(ErrorKind::StrType, self)),
}
}
Expand Down
14 changes: 2 additions & 12 deletions src/input/input_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::str::from_utf8;
use pyo3::exceptions::PyAttributeError;
use pyo3::prelude::*;
use pyo3::types::{
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFrozenSet, PyInt, PyList, PyMapping,
PySequence, PySet, PyString, PyTime, PyTuple, PyType,
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFrozenSet, PyList, PyMapping, PySequence,
PySet, PyString, PyTime, PyTuple, PyType,
};
use pyo3::{intern, AsPyPointer};

Expand Down Expand Up @@ -113,16 +113,6 @@ impl<'a> Input<'a> for PyAny {
Err(_) => return Err(ValError::new(ErrorKind::StrUnicode, self)),
};
Ok(str.into())
} else if self.cast_as::<PyBool>().is_ok() {
// do this before int and float parsing as `False` is cast to `0` and we don't want False to
// be returned as a string
Err(ValError::new(ErrorKind::StrType, self))
} else if let Ok(int) = self.cast_as::<PyInt>() {
let int = i64::extract(int)?;
Ok(int.to_string().into())
} else if let Ok(float) = f64::extract(self) {
// don't cast_as here so Decimals are covered - internally f64:extract uses PyFloat_AsDouble
Ok(float.to_string().into())
} else {
Err(ValError::new(ErrorKind::StrType, self))
}
Expand Down
6 changes: 0 additions & 6 deletions src/input/return_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,6 @@ impl<'a> EitherString<'a> {
}
}

impl<'a> From<String> for EitherString<'a> {
fn from(data: String) -> Self {
Self::Cow(Cow::Owned(data))
}
}

impl<'a> From<&'a str> for EitherString<'a> {
fn from(data: &'a str) -> Self {
Self::Cow(Cow::Borrowed(data))
Expand Down
2 changes: 1 addition & 1 deletion tests/benchmarks/test_complete_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def test_complete_invalid():
lax_validator = SchemaValidator(lax_schema)
with pytest.raises(ValidationError) as exc_info:
lax_validator.validate_python(input_data_wrong())
assert len(exc_info.value.errors()) == 736
assert len(exc_info.value.errors()) == 738

model = pydantic_model()
if model is None:
Expand Down
2 changes: 1 addition & 1 deletion tests/benchmarks/test_micro_benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ def test_frozenset_of_ints_core(benchmark):
benchmark(v.validate_python, frozenset_of_ints)


dict_of_ints_data = ({i: i for i in range(1000)}, {i: str(i) for i in range(1000)})
dict_of_ints_data = ({str(i): i for i in range(1000)}, {str(i): str(i) for i in range(1000)})


@skip_pydantic
Expand Down
12 changes: 7 additions & 5 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ def test_null():


def test_str():
assert SchemaValidator({'type': 'str'}).validate_json('"foobar"') == 'foobar'
assert SchemaValidator({'type': 'str'}).validate_json('123') == '123'
s = SchemaValidator({'type': 'str'})
assert s.validate_json('"foobar"') == 'foobar'
with pytest.raises(ValidationError, match=r'Input should be a valid string \[kind=str_type,'):
SchemaValidator({'type': 'str'}).validate_json('false')
s.validate_json('false')
with pytest.raises(ValidationError, match=r'Input should be a valid string \[kind=str_type,'):
s.validate_json('123')


@pytest.mark.parametrize(
Expand All @@ -53,8 +55,8 @@ def test_model():
)

# language=json
input_str = '{"field_a": 123, "field_b": 1}'
assert v.validate_json(input_str) == {'field_a': '123', 'field_b': 1}
input_str = '{"field_a": "abc", "field_b": 1}'
assert v.validate_json(input_str) == {'field_a': 'abc', 'field_b': 1}


def test_float_no_remainder():
Expand Down
4 changes: 2 additions & 2 deletions tests/validators/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def test_dict(py_and_json: PyAndJson):
@pytest.mark.parametrize(
'input_value,expected',
[
({'1': 1, '2': 2}, {'1': '1', '2': '2'}),
(OrderedDict(a=1, b=2), {'a': '1', 'b': '2'}),
({'1': b'1', '2': b'2'}, {'1': '1', '2': '2'}),
(OrderedDict(a=b'1', b='2'), {'a': '1', 'b': '2'}),
({}, {}),
('foobar', Err("Input should be a valid dictionary [kind=dict_type, input_value='foobar', input_type=str]")),
([], Err('Input should be a valid dictionary [kind=dict_type,')),
Expand Down
12 changes: 6 additions & 6 deletions tests/validators/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def f(input_value, **kwargs):
}
)

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


Expand All @@ -192,7 +192,7 @@ def f(input_value, **kwargs):
{'config_choose_priority': 2},
)

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


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

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

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


Expand Down Expand Up @@ -241,7 +241,7 @@ def f(input_value, **kwargs):

m = {'field_a': 'test', 'more': 'foobar'}
assert v.validate_python({'field_a': 'test'}) == m
assert v.validate_assignment('field_a', 456, m) == {'field_a': '456', 'more': 'foobar'}
assert v.validate_assignment('field_a', b'abc', m) == {'field_a': 'abc', 'more': 'foobar'}


def test_function_wrong_sig():
Expand Down Expand Up @@ -279,9 +279,9 @@ def __validate__(cls, input_value, **kwargs):
assert isinstance(f, Foobar)
assert f.a == 'foofoo'

f = v.validate_python(1)
f = v.validate_python(b'a')
assert isinstance(f, Foobar)
assert f.a == '11'
assert f.a == 'aa'

with pytest.raises(ValidationError) as exc_info:
v.validate_python(True)
Expand Down
31 changes: 25 additions & 6 deletions tests/validators/test_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
'input_value,expected',
[
('foobar', 'foobar'),
(123, '123'),
(123.456, '123.456'),
(123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
(123.456, Err('Input should be a valid string [kind=str_type, input_value=123.456, input_type=float]')),
(False, Err('Input should be a valid string [kind=str_type')),
(True, Err('Input should be a valid string [kind=str_type')),
([], Err('Input should be a valid string [kind=str_type, input_value=[], input_type=list]')),
Expand Down Expand Up @@ -46,8 +46,11 @@ def test_str(py_and_json: PyAndJson, input_value, expected):
),
# null bytes are very annoying, but we can't really block them here
(b'\x00', '\x00'),
(123, '123'),
(Decimal('123'), '123'),
(123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
(
Decimal('123'),
Err("Input should be a valid string [kind=str_type, input_value=Decimal('123'), input_type=Decimal]"),
),
],
)
def test_str_not_json(input_value, expected):
Expand All @@ -62,9 +65,8 @@ def test_str_not_json(input_value, expected):
@pytest.mark.parametrize(
'kwargs,input_value,expected',
[
({}, 123, '123'),
({}, 'abc', 'abc'),
({'strict': True}, 'Foobar', 'Foobar'),
({'strict': True}, 123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
({'to_upper': True}, 'fooBar', 'FOOBAR'),
({'to_lower': True}, 'fooBar', 'foobar'),
({'strip_whitespace': True}, ' foobar ', 'foobar'),
Expand Down Expand Up @@ -93,6 +95,23 @@ def test_constrained_str(py_and_json: PyAndJson, kwargs: Dict[str, Any], input_v
assert v.validate_test(input_value) == expected


@pytest.mark.parametrize(
'kwargs,input_value,expected',
[
({}, b'abc', 'abc'),
({'strict': True}, 'Foobar', 'Foobar'),
({'strict': True}, 123, Err('Input should be a valid string [kind=str_type, input_value=123, input_type=int]')),
],
)
def test_constrained_str_py_only(kwargs: Dict[str, Any], input_value, expected):
v = SchemaValidator({'type': 'str', **kwargs})
if isinstance(expected, Err):
with pytest.raises(ValidationError, match=re.escape(expected.message)):
v.validate_python(input_value)
else:
assert v.validate_python(input_value) == expected


def test_unicode_error():
# `.to_str()` Returns a `UnicodeEncodeError` if the input is not valid unicode (containing unpaired surrogates).
# https://github.com/PyO3/pyo3/blob/6503128442b8f3e767c663a6a8d96376d7fb603d/src/types/string.rs#L477
Expand Down
17 changes: 9 additions & 8 deletions tests/validators/test_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def test_any_no_copy():
'mode,items,input_value,expected',
[
('variable', {'type': 'int'}, (1, 2, '33'), (1, 2, 33)),
('variable', {'type': 'str'}, (1, 2, '33'), ('1', '2', '33')),
('positional', [{'type': 'int'}, {'type': 'str'}, {'type': 'float'}], (1, 2, 33), (1, '2', 33.0)),
('variable', {'type': 'str'}, (b'1', b'2', '33'), ('1', '2', '33')),
('positional', [{'type': 'int'}, {'type': 'str'}, {'type': 'float'}], (1, b'a', 33), (1, 'a', 33.0)),
],
)
def test_tuple_strict_passes_with_tuple(mode, items, input_value, expected):
Expand Down Expand Up @@ -227,7 +227,7 @@ def test_union_tuple_list(input_value, expected):
[
((1, 2, 3), (1, 2, 3)),
(('a', 'b', 'c'), ('a', 'b', 'c')),
(('a', 1, 'c'), ('a', '1', 'c')),
(('a', b'a', 'c'), ('a', 'a', 'c')),
(
[5],
Err(
Expand All @@ -251,6 +251,7 @@ def test_union_tuple_list(input_value, expected):
),
),
],
ids=repr,
)
def test_union_tuple_var_len(input_value, expected):
v = SchemaValidator(
Expand All @@ -276,7 +277,6 @@ def test_union_tuple_var_len(input_value, expected):
[
((1, 2, 3), (1, 2, 3)),
(('a', 'b', 'c'), ('a', 'b', 'c')),
(('a', 1, 'c'), ('a', '1', 'c')),
(
[5, '1', 1],
Err(
Expand All @@ -298,6 +298,7 @@ def test_union_tuple_var_len(input_value, expected):
),
),
],
ids=repr,
)
def test_union_tuple_fix_len(input_value, expected):
v = SchemaValidator(
Expand Down Expand Up @@ -349,10 +350,10 @@ def test_tuple_fix_extra():

def test_tuple_fix_extra_any():
v = SchemaValidator({'type': 'tuple', 'mode': 'positional', 'items_schema': ['str'], 'extra_schema': 'any'})
assert v.validate_python([1]) == ('1',)
assert v.validate_python([1, 2]) == ('1', 2)
assert v.validate_python((1, 2)) == ('1', 2)
assert v.validate_python([1, 2, b'3']) == ('1', 2, b'3')
assert v.validate_python([b'1']) == ('1',)
assert v.validate_python([b'1', 2]) == ('1', 2)
assert v.validate_python((b'1', 2)) == ('1', 2)
assert v.validate_python([b'1', 2, b'3']) == ('1', 2, b'3')
with pytest.raises(ValidationError) as exc_info:
v.validate_python([])
assert exc_info.value.errors() == [{'kind': 'missing', 'loc': [0], 'message': 'Field required', 'input_value': []}]
22 changes: 11 additions & 11 deletions tests/validators/test_typed_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_simple():
}
)

assert v.validate_python({'field_a': 123, 'field_b': 1}) == {'field_a': '123', 'field_b': 1}
assert v.validate_python({'field_a': b'abc', 'field_b': 1}) == {'field_a': 'abc', 'field_b': 1}


def test_strict():
Expand Down Expand Up @@ -77,9 +77,9 @@ def test_with_default():
}
)

assert v.validate_python({'field_a': 123}) == ({'field_a': '123', 'field_b': 666}, {'field_a'})
assert v.validate_python({'field_a': 123, 'field_b': 1}) == (
{'field_a': '123', 'field_b': 1},
assert v.validate_python({'field_a': b'abc'}) == ({'field_a': 'abc', 'field_b': 666}, {'field_a'})
assert v.validate_python({'field_a': b'abc', 'field_b': 1}) == (
{'field_a': 'abc', 'field_b': 1},
{'field_b', 'field_a'},
)

Expand All @@ -92,13 +92,13 @@ def test_missing_error():
}
)
with pytest.raises(ValidationError) as exc_info:
v.validate_python({'field_a': 123})
v.validate_python({'field_a': b'abc'})
assert (
str(exc_info.value)
== """\
1 validation error for typed-dict
field_b
Field required [kind=missing, input_value={'field_a': 123}, input_type=dict]"""
Field required [kind=missing, input_value={'field_a': b'abc'}, input_type=dict]"""
)


Expand Down Expand Up @@ -141,7 +141,7 @@ def test_ignore_extra():
}
)

assert v.validate_python({'field_a': 123, 'field_b': 1, 'field_c': 123}) == (
assert v.validate_python({'field_a': b'123', 'field_b': 1, 'field_c': 123}) == (
{'field_a': '123', 'field_b': 1},
{'field_b', 'field_a'},
)
Expand All @@ -158,7 +158,7 @@ def test_forbid_extra():
)

with pytest.raises(ValidationError) as exc_info:
v.validate_python({'field_a': 123, 'field_b': 1})
v.validate_python({'field_a': 'abc', 'field_b': 1})

assert exc_info.value.errors() == [
{'kind': 'extra_forbidden', 'loc': ['field_b'], 'message': 'Extra inputs are not permitted', 'input_value': 1}
Expand All @@ -175,8 +175,8 @@ def test_allow_extra():
}
)

assert v.validate_python({'field_a': 123, 'field_b': (1, 2)}) == (
{'field_a': '123', 'field_b': (1, 2)},
assert v.validate_python({'field_a': b'abc', 'field_b': (1, 2)}) == (
{'field_a': 'abc', 'field_b': (1, 2)},
{'field_a', 'field_b'},
)

Expand Down Expand Up @@ -238,7 +238,7 @@ def test_validate_assignment():

assert v.validate_python({'field_a': 'test'}) == ({'field_a': 'test'}, {'field_a'})

assert v.validate_assignment('field_a', 456, {'field_a': 'test'}) == ({'field_a': '456'}, {'field_a'})
assert v.validate_assignment('field_a', b'abc', {'field_a': 'test'}) == ({'field_a': 'abc'}, {'field_a'})


def test_validate_assignment_functions():
Expand Down