diff --git a/src/validators/nullable.rs b/src/validators/nullable.rs index b01cbd365..d22473c03 100644 --- a/src/validators/nullable.rs +++ b/src/validators/nullable.rs @@ -8,6 +8,7 @@ use crate::tools::SchemaDict; use super::ValidationState; use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuilder, Validator}; +use crate::errors::{ErrorType, ValError, ValLineError}; #[derive(Debug)] pub struct NullableValidator { @@ -41,7 +42,17 @@ impl Validator for NullableValidator { ) -> ValResult> { match input.is_none() { true => Ok(py.None()), - false => self.validator.validate(py, input, state), + false => { + let val_res = self.validator.validate(py, input, state); + match val_res { + Ok(obj) => Ok(obj), + Err(ValError::LineErrors(mut lines)) => { + lines.push(ValLineError::new(ErrorType::NoneRequired { context: None }, input)); + Err(ValError::from(lines)) + } + Err(err) => Err(err), + } + } } } diff --git a/tests/benchmarks/test_complete_benchmark.py b/tests/benchmarks/test_complete_benchmark.py index 6ef046529..380a1dcca 100644 --- a/tests/benchmarks/test_complete_benchmark.py +++ b/tests/benchmarks/test_complete_benchmark.py @@ -84,7 +84,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(include_url=False)) == 739 + assert len(exc_info.value.errors(include_url=False)) == 841 @pytest.mark.benchmark(group='complete') diff --git a/tests/test_hypothesis.py b/tests/test_hypothesis.py index 5a24d6808..16e46a664 100644 --- a/tests/test_hypothesis.py +++ b/tests/test_hypothesis.py @@ -117,14 +117,20 @@ def test_definition_cycles(definition_schema, data): try: assert definition_schema.validate_python(data) == data except ValidationError as exc: - assert exc.errors(include_url=False) == [ - { - 'type': 'recursion_loop', - 'loc': IsTuple(length=(1, None)), - 'msg': 'Recursion error - cyclic reference detected', - 'input': AnyThing(), - } - ] + errors = exc.errors(include_url=False) + + # 1st error-line should be the 'recursion-loop' error + assert errors[0] == { + 'type': 'recursion_loop', + 'loc': IsTuple(length=(1, None)), + 'msg': 'Recursion error - cyclic reference detected', + 'input': AnyThing(), + } + + # There is one 'none-required' error per sub-branch location + assert all(e['type'] == 'none_required' for e in errors[1:]) + nb_sub_branch_locs = len(data) - 1 + assert nb_sub_branch_locs == len(errors[:1]) def test_definition_broken(definition_schema): diff --git a/tests/validators/test_definitions_recursive.py b/tests/validators/test_definitions_recursive.py index b36d4aa07..f8ce129e2 100644 --- a/tests/validators/test_definitions_recursive.py +++ b/tests/validators/test_definitions_recursive.py @@ -326,7 +326,13 @@ def test_recursion_branch(): 'loc': ('branch',), 'msg': 'Recursion error - cyclic reference detected', 'input': {'name': 'recursive', 'branch': IsPartialDict(name='recursive')}, - } + }, + { + 'type': 'none_required', + 'loc': ('branch',), + 'msg': 'Input should be None', + 'input': {'name': 'recursive', 'branch': IsPartialDict(name='recursive')}, + }, ] @@ -375,7 +381,13 @@ def test_recursion_branch_from_attributes(): 'loc': ('branch',), 'msg': 'Recursion error - cyclic reference detected', 'input': HasAttributes(name='root', branch=AnyThing()), - } + }, + { + 'type': 'none_required', + 'loc': ('branch',), + 'msg': 'Input should be None', + 'input': HasAttributes(name='root', branch=AnyThing()), + }, ] @@ -475,12 +487,30 @@ def test_multiple_tuple_recursion(multiple_tuple_schema: SchemaValidator): 'msg': 'Recursion error - cyclic reference detected', 'input': [1, IsList(length=2)], }, + { + 'type': 'none_required', + 'loc': ('f1', 1), + 'msg': 'Input should be None', + 'input': [1, IsList(length=2)], + }, { 'type': 'recursion_loop', 'loc': ('f2', 1), 'msg': 'Recursion error - cyclic reference detected', 'input': [1, IsList(length=2)], }, + { + 'type': 'none_required', + 'loc': ('f2', 1), + 'msg': 'Input should be None', + 'input': [1, IsList(length=2)], + }, + { + 'type': 'none_required', + 'loc': ('f2',), + 'msg': 'Input should be None', + 'input': [1, IsList(length=2)], + }, ] @@ -497,12 +527,30 @@ def test_multiple_tuple_recursion_once(multiple_tuple_schema: SchemaValidator): 'msg': 'Recursion error - cyclic reference detected', 'input': [1, IsList(length=2)], }, + { + 'type': 'none_required', + 'loc': ('f1', 1), + 'msg': 'Input should be None', + 'input': [1, IsList(length=2)], + }, { 'type': 'recursion_loop', 'loc': ('f2', 1), 'msg': 'Recursion error - cyclic reference detected', 'input': [1, IsList(length=2)], }, + { + 'type': 'none_required', + 'loc': ('f2', 1), + 'msg': 'Input should be None', + 'input': [1, IsList(length=2)], + }, + { + 'type': 'none_required', + 'loc': ('f2',), + 'msg': 'Input should be None', + 'input': [1, IsList(length=2)], + }, ] @@ -539,7 +587,13 @@ def wrap_func(input_value, validator, info): 'loc': (1,), 'msg': 'Recursion error - cyclic reference detected', 'input': IsList(positions={0: 1}, length=2), - } + }, + { + 'type': 'none_required', + 'loc': (1,), + 'msg': 'Input should be None', + 'input': IsList(positions={0: 1}, length=2), + }, ] @@ -927,7 +981,19 @@ def test_cyclic_data() -> None: 'loc': ('b', 'a'), 'msg': 'Recursion error - cyclic reference detected', 'input': cyclic_data, - } + }, + { + 'type': 'none_required', + 'loc': ('b', 'a'), + 'msg': 'Input should be None', + 'input': cyclic_data, + }, + { + 'type': 'none_required', + 'loc': ('b',), + 'msg': 'Input should be None', + 'input': cyclic_data['b'], + }, ] @@ -977,7 +1043,25 @@ def test_cyclic_data_threeway() -> None: 'loc': ('b', 'c', 'a'), 'msg': 'Recursion error - cyclic reference detected', 'input': cyclic_data, - } + }, + { + 'type': 'none_required', + 'loc': ('b', 'c', 'a'), + 'msg': 'Input should be None', + 'input': cyclic_data, + }, + { + 'type': 'none_required', + 'loc': ('b', 'c'), + 'msg': 'Input should be None', + 'input': cyclic_data['b']['c'], + }, + { + 'type': 'none_required', + 'loc': ('b',), + 'msg': 'Input should be None', + 'input': cyclic_data['b'], + }, ] @@ -1051,6 +1135,12 @@ def test_complex_recursive_type() -> None: 'msg': 'Input should be a valid boolean', 'input': datetime.date(1992, 12, 11), }, + { + 'type': 'none_required', + 'loc': ('dict[str,...]', 'a'), + 'msg': 'Input should be None', + 'input': datetime.date(1992, 12, 11), + }, { 'type': 'string_type', 'loc': ('str',), @@ -1075,6 +1165,12 @@ def test_complex_recursive_type() -> None: 'msg': 'Input should be a valid boolean', 'input': {'a': datetime.date(1992, 12, 11)}, }, + { + 'type': 'none_required', + 'loc': (), + 'msg': 'Input should be None', + 'input': {'a': datetime.date(1992, 12, 11)}, + }, ] diff --git a/tests/validators/test_nullable.py b/tests/validators/test_nullable.py index af73a8875..24d469ad7 100644 --- a/tests/validators/test_nullable.py +++ b/tests/validators/test_nullable.py @@ -21,7 +21,13 @@ def test_nullable(): 'loc': (), 'msg': 'Input should be a valid integer, unable to parse string as an integer', 'input': 'hello', - } + }, + { + 'type': 'none_required', + 'loc': (), + 'msg': 'Input should be None', + 'input': 'hello', + }, ]