diff --git a/connexion/decorators/validation.py b/connexion/decorators/validation.py index a2e7239f1..c3ca4c4a2 100644 --- a/connexion/decorators/validation.py +++ b/connexion/decorators/validation.py @@ -5,7 +5,8 @@ import sys import six -from jsonschema import Draft4Validator, ValidationError, draft4_format_checker +from jsonschema import (Draft4Validator, ValidationError, + draft4_format_checker, validators) from werkzeug import FileStorage from ..exceptions import ExtraParameterProblem @@ -74,6 +75,38 @@ def validate_parameter_list(request_params, spec_params): return request_params.difference(spec_params) +def extend_with_nullable_support(validator_class): + """Add support for null values in body. + + It adds property validator to given validator_class. + + :param validator_class: validator to add nullable support + :type validator_class: jsonschema.IValidator + :return: new validator with added nullable support in properties + :rtype: jsonschema.IValidator + """ + validate_properties = validator_class.VALIDATORS['properties'] + + def nullable_support(validator, properties, instance, schema): + null_properties = {} + for property_, subschema in six.iteritems(properties): + if isinstance(instance, collections.Iterable) and \ + property_ in instance and \ + instance[property_] is None and \ + subschema.get('x-nullable') is True: + # exclude from following validation + null_properties[property_] = instance.pop(property_) + for error in validate_properties(validator, properties, instance, schema): + yield error + # add null properties back + if null_properties: + instance.update(null_properties) + return validators.extend(validator_class, {'properties': nullable_support}) + + +Draft4ValidatorSupportNullable = extend_with_nullable_support(Draft4Validator) + + class RequestBodyValidator(object): def __init__(self, schema, consumes, api, is_null_value_valid=False, validator=None, strict_validation=False): @@ -90,7 +123,7 @@ def __init__(self, schema, consumes, api, is_null_value_valid=False, validator=N self.schema = schema self.has_default = schema.get('default', False) self.is_null_value_valid = is_null_value_valid - validatorClass = validator or Draft4Validator + validatorClass = validator or Draft4ValidatorSupportNullable self.validator = validatorClass(schema, format_checker=draft4_format_checker) self.api = api self.strict_validation = strict_validation @@ -160,7 +193,7 @@ def __init__(self, schema, validator=None): against API schema. Default is jsonschema.Draft4Validator. :type validator: jsonschema.IValidator """ - ValidatorClass = validator or Draft4Validator + ValidatorClass = validator or Draft4ValidatorSupportNullable self.validator = ValidatorClass(schema, format_checker=draft4_format_checker) def validate_schema(self, data, url): diff --git a/tests/decorators/test_validation.py b/tests/decorators/test_validation.py index 5e386050c..4f84a629f 100644 --- a/tests/decorators/test_validation.py +++ b/tests/decorators/test_validation.py @@ -1,4 +1,8 @@ -from connexion.decorators.validation import ParameterValidator +from jsonschema import ValidationError + +import pytest +from connexion.decorators.validation import (Draft4ValidatorSupportNullable, + ParameterValidator) from mock import MagicMock @@ -46,3 +50,32 @@ def test_invalid_type_value_error(monkeypatch): value = {'test': 1, 'second': 2} result = ParameterValidator.validate_parameter('formdata', value, {'type': 'boolean', 'name': 'foo'}) assert result == "Wrong type, expected 'boolean' for formdata parameter 'foo'" + +def test_support_nullable_properties(): + schema = { + "type": "object", + "properties": {"foo": {"type": "string", "x-nullable": True}}, + } + try: + Draft4ValidatorSupportNullable(schema).validate({"foo": None}) + except ValidationError: + pytest.fail("Shouldn't raise ValidationError") + + +def test_support_nullable_properties_raises_validation_error(): + schema = { + "type": "object", + "properties": {"foo": {"type": "string", "x-nullable": False}}, + } + + with pytest.raises(ValidationError): + Draft4ValidatorSupportNullable(schema).validate({"foo": None}) + + +def test_support_nullable_properties_not_iterable(): + schema = { + "type": "object", + "properties": {"foo": {"type": "string", "x-nullable": True}}, + } + with pytest.raises(ValidationError): + Draft4ValidatorSupportNullable(schema).validate(12345)