diff --git a/openapi_core/schema/media_types/models.py b/openapi_core/schema/media_types/models.py index b84f8f2..9a536c8 100644 --- a/openapi_core/schema/media_types/models.py +++ b/openapi_core/schema/media_types/models.py @@ -3,11 +3,10 @@ from collections import defaultdict from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue from openapi_core.schema.media_types.util import json_loads -from openapi_core.schema.schemas.exceptions import ( - ValidateError, -) from openapi_core.casting.schemas.exceptions import CastError -from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError +from openapi_core.unmarshalling.schemas.exceptions import ( + UnmarshalError, ValidateError, +) MEDIA_TYPE_DESERIALIZERS = { @@ -53,13 +52,8 @@ class MediaType(object): if not self.schema: return value - try: - self.schema.validate(value, resolver=resolver) - except ValidateError as exc: - raise InvalidMediaTypeValue(exc) - try: return self.schema.unmarshal( - value, custom_formatters=custom_formatters) - except UnmarshalError as exc: + value, resolver=resolver, custom_formatters=custom_formatters) + except (ValidateError, UnmarshalError) as exc: raise InvalidMediaTypeValue(exc) diff --git a/openapi_core/schema/parameters/models.py b/openapi_core/schema/parameters/models.py index c06e198..768f72e 100644 --- a/openapi_core/schema/parameters/models.py +++ b/openapi_core/schema/parameters/models.py @@ -10,11 +10,10 @@ from openapi_core.schema.parameters.exceptions import ( EmptyParameterValue, ) from openapi_core.schema.schemas.enums import SchemaType -from openapi_core.schema.schemas.exceptions import ( - ValidateError, -) from openapi_core.casting.schemas.exceptions import CastError -from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError +from openapi_core.unmarshalling.schemas.exceptions import ( + UnmarshalError, ValidateError, +) log = logging.getLogger(__name__) @@ -120,16 +119,11 @@ class Parameter(object): if not self.schema: return value - try: - self.schema.validate(value, resolver=resolver) - except ValidateError as exc: - raise InvalidParameterValue(self.name, exc) - try: return self.schema.unmarshal( value, + resolver=resolver, custom_formatters=custom_formatters, - strict=True, ) - except UnmarshalError as exc: + except (ValidateError, UnmarshalError) as exc: raise InvalidParameterValue(self.name, exc) diff --git a/openapi_core/schema/schemas/exceptions.py b/openapi_core/schema/schemas/exceptions.py index 5b0d808..1487bf7 100644 --- a/openapi_core/schema/schemas/exceptions.py +++ b/openapi_core/schema/schemas/exceptions.py @@ -1,31 +1,5 @@ -import attr - from openapi_core.schema.exceptions import OpenAPIMappingError class OpenAPISchemaError(OpenAPIMappingError): pass - - -class ValidateError(OpenAPISchemaError): - """Schema validate operation error""" - pass - - -@attr.s(hash=True) -class InvalidSchemaValue(ValidateError): - value = attr.ib() - type = attr.ib() - _schema_errors = attr.ib(default=None) - _schema_errors_iter = attr.ib(factory=list) - - @property - def schema_errors(self): - if self._schema_errors is None: - self._schema_errors = list(self._schema_errors_iter) - return self._schema_errors - - def __str__(self): - return ( - "Value {value} not valid for schema of type {type}: {errors}" - ).format(value=self.value, type=self.type, errors=self.schema_errors) diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index b4190e1..af780f3 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -3,13 +3,8 @@ import attr import logging import re -from jsonschema.exceptions import ValidationError - -from openapi_core.schema.schemas._format import oas30_format_checker from openapi_core.schema.schemas.enums import SchemaType -from openapi_core.schema.schemas.exceptions import InvalidSchemaValue from openapi_core.schema.schemas.types import NoValue -from openapi_core.schema.schemas.validators import OAS30Validator from openapi_core.unmarshalling.schemas.exceptions import ( UnmarshalValueError, ) @@ -110,30 +105,15 @@ class Schema(object): except (ValueError, TypeError): raise CastError(value, self.type) - def get_validator(self, resolver=None): - return OAS30Validator( - self.__dict__, - resolver=resolver, format_checker=oas30_format_checker, - ) - - def validate(self, value, resolver=None): - validator = self.get_validator(resolver=resolver) - try: - return validator.validate(value) - except ValidationError: - errors_iter = validator.iter_errors(value) - raise InvalidSchemaValue( - value, self.type, schema_errors_iter=errors_iter) - - def unmarshal(self, value, custom_formatters=None, strict=True): + def unmarshal(self, value, resolver=None, custom_formatters=None): """Unmarshal parameter from the value.""" from openapi_core.unmarshalling.schemas.factories import ( SchemaUnmarshallersFactory, ) unmarshallers_factory = SchemaUnmarshallersFactory( - custom_formatters) + resolver, custom_formatters) unmarshaller = unmarshallers_factory.create(self) try: - return unmarshaller(value, strict=strict) + return unmarshaller(value) except ValueError as exc: raise UnmarshalValueError(value, self.type, exc) diff --git a/openapi_core/schema_validator/__init__.py b/openapi_core/schema_validator/__init__.py new file mode 100644 index 0000000..a86664a --- /dev/null +++ b/openapi_core/schema_validator/__init__.py @@ -0,0 +1,4 @@ +from openapi_core.schema_validator._format import oas30_format_checker +from openapi_core.schema_validator.validators import OAS30Validator + +__all__ = ['OAS30Validator', 'oas30_format_checker'] diff --git a/openapi_core/schema/schemas/_format.py b/openapi_core/schema_validator/_format.py similarity index 58% rename from openapi_core/schema/schemas/_format.py rename to openapi_core/schema_validator/_format.py index 61704bd..35f0653 100644 --- a/openapi_core/schema/schemas/_format.py +++ b/openapi_core/schema_validator/_format.py @@ -28,60 +28,41 @@ else: DATETIME_RAISES += (ValueError, TypeError) -class StrictFormatChecker(FormatChecker): - - def check(self, instance, format): - if format not in self.checkers: - raise FormatError( - "Format checker for %r format not found" % (format, )) - return super(StrictFormatChecker, self).check( - instance, format) - - -oas30_format_checker = StrictFormatChecker() - - -@oas30_format_checker.checks('int32') def is_int32(instance): return isinstance(instance, integer_types) -@oas30_format_checker.checks('int64') def is_int64(instance): return isinstance(instance, integer_types) -@oas30_format_checker.checks('float') def is_float(instance): return isinstance(instance, float) -@oas30_format_checker.checks('double') def is_double(instance): # float has double precision in Python # It's double in CPython and Jython return isinstance(instance, float) -@oas30_format_checker.checks('binary') def is_binary(instance): return isinstance(instance, binary_type) -@oas30_format_checker.checks('byte', raises=(binascii.Error, TypeError)) def is_byte(instance): if isinstance(instance, text_type): instance = instance.encode() - return b64encode(b64decode(instance)) == instance - - -@oas30_format_checker.checks("date-time", raises=DATETIME_RAISES) -def is_datetime(instance): - if isinstance(instance, binary_type): + try: + return b64encode(b64decode(instance)) == instance + except TypeError: + return False + + +def is_datetime(instance): + if not isinstance(instance, (binary_type, text_type)): return False - if not isinstance(instance, text_type): - return True if DATETIME_HAS_STRICT_RFC3339: return strict_rfc3339.validate_rfc3339(instance) @@ -92,24 +73,63 @@ def is_datetime(instance): return True -@oas30_format_checker.checks("date", raises=ValueError) def is_date(instance): - if isinstance(instance, binary_type): + if not isinstance(instance, (binary_type, text_type)): return False - if not isinstance(instance, text_type): - return True + + if isinstance(instance, binary_type): + instance = instance.decode() + return datetime.strptime(instance, "%Y-%m-%d") -@oas30_format_checker.checks("uuid", raises=AttributeError) def is_uuid(instance): - if isinstance(instance, binary_type): - return False - if not isinstance(instance, text_type): - return True - try: - uuid_obj = UUID(instance) - except ValueError: + if not isinstance(instance, (binary_type, text_type)): return False - return text_type(uuid_obj) == instance + if isinstance(instance, binary_type): + instance = instance.decode() + + return text_type(UUID(instance)) == instance + + +def is_password(instance): + return True + + +class OASFormatChecker(FormatChecker): + + checkers = { + 'int32': (is_int32, ()), + 'int64': (is_int64, ()), + 'float': (is_float, ()), + 'double': (is_double, ()), + 'byte': (is_byte, (binascii.Error, TypeError)), + 'binary': (is_binary, ()), + 'date': (is_date, (ValueError, )), + 'date-time': (is_datetime, DATETIME_RAISES), + 'password': (is_password, ()), + # non standard + 'uuid': (is_uuid, (AttributeError, ValueError)), + } + + def check(self, instance, format): + if format not in self.checkers: + raise FormatError( + "Format checker for %r format not found" % (format, )) + + func, raises = self.checkers[format] + result, cause = None, None + try: + result = func(instance) + except raises as e: + cause = e + + if not result: + raise FormatError( + "%r is not a %r" % (instance, format), cause=cause, + ) + return result + + +oas30_format_checker = OASFormatChecker() diff --git a/openapi_core/schema/schemas/_types.py b/openapi_core/schema_validator/_types.py similarity index 100% rename from openapi_core/schema/schemas/_types.py rename to openapi_core/schema_validator/_types.py diff --git a/openapi_core/schema/schemas/_validators.py b/openapi_core/schema_validator/_validators.py similarity index 100% rename from openapi_core/schema/schemas/_validators.py rename to openapi_core/schema_validator/_validators.py diff --git a/openapi_core/schema/schemas/validators.py b/openapi_core/schema_validator/validators.py similarity index 94% rename from openapi_core/schema/schemas/validators.py rename to openapi_core/schema_validator/validators.py index eabe4d4..14f0402 100644 --- a/openapi_core/schema/schemas/validators.py +++ b/openapi_core/schema_validator/validators.py @@ -1,8 +1,8 @@ from jsonschema import _legacy_validators, _utils, _validators from jsonschema.validators import create -from openapi_core.schema.schemas import _types as oas_types -from openapi_core.schema.schemas import _validators as oas_validators +from openapi_core.schema_validator import _types as oas_types +from openapi_core.schema_validator import _validators as oas_validators BaseOAS30Validator = create( diff --git a/openapi_core/unmarshalling/schemas/exceptions.py b/openapi_core/unmarshalling/schemas/exceptions.py index 9b05745..76db72b 100644 --- a/openapi_core/unmarshalling/schemas/exceptions.py +++ b/openapi_core/unmarshalling/schemas/exceptions.py @@ -8,6 +8,11 @@ class UnmarshalError(OpenAPIError): pass +class ValidateError(UnmarshalError): + """Schema validate operation error""" + pass + + class UnmarshallerError(UnmarshalError): """Unmarshaller error""" pass @@ -30,8 +35,20 @@ class UnmarshalValueError(UnmarshalError): @attr.s(hash=True) -class InvalidCustomFormatSchemaValue(UnmarshallerError): - """Value failed to format with custom formatter""" +class InvalidSchemaValue(ValidateError): + value = attr.ib() + type = attr.ib() + schema_errors = attr.ib(factory=tuple) + + def __str__(self): + return ( + "Value {value} not valid for schema of type {type}: {errors}" + ).format(value=self.value, type=self.type, errors=self.schema_errors) + + +@attr.s(hash=True) +class InvalidSchemaFormatValue(UnmarshallerError): + """Value failed to format with formatter""" value = attr.ib() type = attr.ib() original_exception = attr.ib() @@ -53,14 +70,3 @@ class FormatterNotFoundError(UnmarshallerError): def __str__(self): return "Formatter not found for {format} format".format( format=self.type_format) - - -@attr.s(hash=True) -class UnmarshallerStrictTypeError(UnmarshallerError): - value = attr.ib() - types = attr.ib() - - def __str__(self): - types = ', '.join(list(map(str, self.types))) - return "Value {value} is not one of types: {types}".format( - value=self.value, types=types) diff --git a/openapi_core/unmarshalling/schemas/factories.py b/openapi_core/unmarshalling/schemas/factories.py index ad11625..9b53cc9 100644 --- a/openapi_core/unmarshalling/schemas/factories.py +++ b/openapi_core/unmarshalling/schemas/factories.py @@ -1,6 +1,9 @@ +from copy import deepcopy import warnings from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat +from openapi_core.schema_validator import OAS30Validator +from openapi_core.schema_validator import oas30_format_checker from openapi_core.unmarshalling.schemas.exceptions import ( FormatterNotFoundError, ) @@ -25,7 +28,8 @@ class SchemaUnmarshallersFactory(object): SchemaType.ANY: AnyUnmarshaller, } - def __init__(self, custom_formatters=None): + def __init__(self, resolver=None, custom_formatters=None): + self.resolver = resolver if custom_formatters is None: custom_formatters = {} self.custom_formatters = custom_formatters @@ -42,21 +46,31 @@ class SchemaUnmarshallersFactory(object): elif schema_type in self.COMPLEX_UNMARSHALLERS: klass = self.COMPLEX_UNMARSHALLERS[schema_type] - kwargs = dict(schema=schema, unmarshallers_factory=self) + kwargs = dict( + schema=schema, unmarshallers_factory=self) formatter = self.get_formatter(klass.FORMATTERS, schema.format) if formatter is None: raise FormatterNotFoundError(schema.format) - return klass(formatter, **kwargs) + validator = self.get_validator(schema) - def get_formatter(self, formatters, type_format=SchemaFormat.NONE): + return klass(formatter, validator, **kwargs) + + def get_formatter(self, default_formatters, type_format=SchemaFormat.NONE): try: schema_format = SchemaFormat(type_format) except ValueError: return self.custom_formatters.get(type_format) else: - if schema_format == SchemaFormat.NONE: - return lambda x: x - return formatters.get(schema_format) + return default_formatters.get(schema_format) + + def get_validator(self, schema): + format_checker = deepcopy(oas30_format_checker) + for name, formatter in self.custom_formatters.items(): + format_checker.checks(name)(formatter.validate) + return OAS30Validator( + schema.__dict__, + resolver=self.resolver, format_checker=format_checker, + ) diff --git a/openapi_core/unmarshalling/schemas/formatters.py b/openapi_core/unmarshalling/schemas/formatters.py new file mode 100644 index 0000000..b0fed28 --- /dev/null +++ b/openapi_core/unmarshalling/schemas/formatters.py @@ -0,0 +1,18 @@ +class Formatter(object): + + def validate(self, value): + return True + + def unmarshal(self, value): + return value + + @classmethod + def from_callables(cls, validate=None, unmarshal=None): + attrs = {} + if validate is not None: + attrs['validate'] = staticmethod(validate) + if unmarshal is not None: + attrs['unmarshal'] = staticmethod(unmarshal) + + klass = type('Formatter', (cls, ), attrs) + return klass() diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index 7817b5f..1a00026 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -1,19 +1,22 @@ +from functools import partial import logging -from six import text_type, binary_type, integer_types +from six import text_type, binary_type from six import iteritems from openapi_core.extensions.models.factories import ModelFactory from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType -from openapi_core.schema.schemas.exceptions import ( - ValidateError, -) from openapi_core.schema.schemas.types import NoValue -from openapi_core.unmarshalling.schemas.exceptions import ( - UnmarshalError, - InvalidCustomFormatSchemaValue, - UnmarshallerStrictTypeError, +from openapi_core.schema_validator._types import ( + is_array, is_bool, is_integer, + is_object, is_number, is_string, ) +from openapi_core.schema_validator._format import oas30_format_checker +from openapi_core.unmarshalling.schemas.exceptions import ( + UnmarshalError, ValidateError, InvalidSchemaValue, + InvalidSchemaFormatValue, +) +from openapi_core.unmarshalling.schemas.formatters import Formatter from openapi_core.unmarshalling.schemas.util import ( forcebool, format_date, format_datetime, format_byte, format_uuid, format_number, @@ -22,133 +25,142 @@ from openapi_core.unmarshalling.schemas.util import ( log = logging.getLogger(__name__) -class StrictUnmarshaller(object): - - STRICT_TYPES = () - - def __call__(self, value, strict=True): - if strict and not self._is_strict(value): - raise UnmarshallerStrictTypeError(value, self.STRICT_TYPES) - - return value - - def _is_strict(self, value): - if not self.STRICT_TYPES: - return True - - return isinstance(value, self.STRICT_TYPES) - - -class PrimitiveTypeUnmarshaller(StrictUnmarshaller): +class PrimitiveTypeUnmarshaller(object): FORMATTERS = {} - def __init__(self, formatter, schema): + def __init__(self, formatter, validator, schema): self.formatter = formatter + self.validator = validator self.schema = schema - def __call__(self, value=NoValue, strict=True): + def __call__(self, value=NoValue): if value is NoValue: value = self.schema.default if value is None: return - value = super(PrimitiveTypeUnmarshaller, self).__call__( - value, strict=strict) - return self.format(value) + self.validate(value) - def format(self, value): + return self.unmarshal(value) + + def _formatter_validate(self, value): + result = self.formatter.validate(value) + if not result: + raise InvalidSchemaValue(value, self.schema.type) + + def validate(self, value): + errors_iter = self.validator.iter_errors(value) + errors = tuple(errors_iter) + if errors: + raise InvalidSchemaValue( + value, self.schema.type, schema_errors=errors) + + def unmarshal(self, value): try: - return self.formatter(value) + return self.formatter.unmarshal(value) except ValueError as exc: - raise InvalidCustomFormatSchemaValue( + raise InvalidSchemaFormatValue( value, self.schema.format, exc) class StringUnmarshaller(PrimitiveTypeUnmarshaller): - STRICT_TYPES = (text_type, binary_type) FORMATTERS = { - SchemaFormat.NONE: text_type, - SchemaFormat.PASSWORD: text_type, - SchemaFormat.DATE: format_date, - SchemaFormat.DATETIME: format_datetime, - SchemaFormat.BINARY: binary_type, - SchemaFormat.UUID: format_uuid, - SchemaFormat.BYTE: format_byte, + SchemaFormat.NONE: Formatter.from_callables( + partial(is_string, None), text_type), + SchemaFormat.PASSWORD: Formatter.from_callables( + partial(oas30_format_checker.check, format='password'), text_type), + SchemaFormat.DATE: Formatter.from_callables( + partial(oas30_format_checker.check, format='date'), format_date), + SchemaFormat.DATETIME: Formatter.from_callables( + partial(oas30_format_checker.check, format='date-time'), + format_datetime), + SchemaFormat.BINARY: Formatter.from_callables( + partial(oas30_format_checker.check, format='binary'), binary_type), + SchemaFormat.UUID: Formatter.from_callables( + partial(oas30_format_checker.check, format='uuid'), format_uuid), + SchemaFormat.BYTE: Formatter.from_callables( + partial(oas30_format_checker.check, format='byte'), format_byte), } class IntegerUnmarshaller(PrimitiveTypeUnmarshaller): - STRICT_TYPES = integer_types FORMATTERS = { - SchemaFormat.NONE: int, - SchemaFormat.INT32: int, - SchemaFormat.INT64: int, + SchemaFormat.NONE: Formatter.from_callables( + partial(is_integer, None), int), + SchemaFormat.INT32: Formatter.from_callables( + partial(oas30_format_checker.check, format='int32'), int), + SchemaFormat.INT64: Formatter.from_callables( + partial(oas30_format_checker.check, format='int64'), int), } class NumberUnmarshaller(PrimitiveTypeUnmarshaller): - STRICT_TYPES = (float, ) + integer_types FORMATTERS = { - SchemaFormat.NONE: format_number, - SchemaFormat.FLOAT: float, - SchemaFormat.DOUBLE: float, + SchemaFormat.NONE: Formatter.from_callables( + partial(is_number, None), format_number), + SchemaFormat.FLOAT: Formatter.from_callables( + partial(oas30_format_checker.check, format='float'), float), + SchemaFormat.DOUBLE: Formatter.from_callables( + partial(oas30_format_checker.check, format='double'), float), } class BooleanUnmarshaller(PrimitiveTypeUnmarshaller): - STRICT_TYPES = (bool, ) FORMATTERS = { - SchemaFormat.NONE: forcebool, + SchemaFormat.NONE: Formatter.from_callables( + partial(is_bool, None), forcebool), } class ComplexUnmarshaller(PrimitiveTypeUnmarshaller): - def __init__(self, formatter, schema, unmarshallers_factory): - super(ComplexUnmarshaller, self).__init__(formatter, schema) + def __init__(self, formatter, validator, schema, unmarshallers_factory): + super(ComplexUnmarshaller, self).__init__(formatter, validator, schema) self.unmarshallers_factory = unmarshallers_factory class ArrayUnmarshaller(ComplexUnmarshaller): - STRICT_TYPES = (list, tuple) - FORMATTERS = {} + FORMATTERS = { + SchemaFormat.NONE: Formatter.from_callables( + partial(is_array, None), list), + } @property def items_unmarshaller(self): return self.unmarshallers_factory.create(self.schema.items) - def __call__(self, value=NoValue, strict=True): - value = super(ArrayUnmarshaller, self).__call__(value, strict=strict) - - self.unmarshallers_factory.create(self.schema.items) + def __call__(self, value=NoValue): + value = super(ArrayUnmarshaller, self).__call__(value) return list(map(self.items_unmarshaller, value)) class ObjectUnmarshaller(ComplexUnmarshaller): - STRICT_TYPES = (dict, ) - FORMATTERS = {} + FORMATTERS = { + SchemaFormat.NONE: Formatter.from_callables( + partial(is_object, None), dict), + } @property def model_factory(self): return ModelFactory() - def __call__(self, value=NoValue, strict=True): - value = super(ObjectUnmarshaller, self).__call__(value, strict=strict) + def __call__(self, value=NoValue): + value = super(ObjectUnmarshaller, self).__call__(value) if self.schema.one_of: properties = None for one_of_schema in self.schema.one_of: try: unmarshalled = self._unmarshal_properties( - value, one_of_schema, strict=strict) + value, one_of_schema) except (UnmarshalError, ValueError): pass else: @@ -169,8 +181,7 @@ class ObjectUnmarshaller(ComplexUnmarshaller): return properties - def _unmarshal_properties( - self, value=NoValue, one_of_schema=None, strict=True): + def _unmarshal_properties(self, value=NoValue, one_of_schema=None): all_props = self.schema.get_all_properties() all_props_names = self.schema.get_all_properties_names() @@ -187,8 +198,7 @@ class ObjectUnmarshaller(ComplexUnmarshaller): for prop_name in extra_props: prop_value = value[prop_name] properties[prop_name] = self.unmarshallers_factory.create( - self.schema.additional_properties)( - prop_value, strict=strict) + self.schema.additional_properties)(prop_value) for prop_name, prop in iteritems(all_props): try: @@ -199,31 +209,37 @@ class ObjectUnmarshaller(ComplexUnmarshaller): prop_value = prop.default properties[prop_name] = self.unmarshallers_factory.create( - prop)(prop_value, strict=strict) + prop)(prop_value) return properties class AnyUnmarshaller(ComplexUnmarshaller): + FORMATTERS = { + SchemaFormat.NONE: Formatter(), + } + SCHEMA_TYPES_ORDER = [ SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN, SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING, ] - def __call__(self, value=NoValue, strict=True): + def __call__(self, value=NoValue): one_of_schema = self._get_one_of_schema(value) if one_of_schema: - return self.unmarshallers_factory.create(one_of_schema)( - value, strict=strict) + return self.unmarshallers_factory.create(one_of_schema)(value) for schema_type in self.SCHEMA_TYPES_ORDER: + unmarshaller = self.unmarshallers_factory.create( + self.schema, type_override=schema_type) + # validate with validator of formatter (usualy type validator) try: - unmarshaller = self.unmarshallers_factory.create( - self.schema, type_override=schema_type) - return unmarshaller(value, strict=strict) - except (UnmarshalError, ValueError): + unmarshaller._formatter_validate(value) + except ValidateError: continue + else: + return unmarshaller(value) log.warning("failed to unmarshal any type") return value @@ -232,8 +248,9 @@ class AnyUnmarshaller(ComplexUnmarshaller): if not self.schema.one_of: return for subschema in self.schema.one_of: + unmarshaller = self.unmarshallers_factory.create(subschema) try: - subschema.validate(value) + unmarshaller.validate(value) except ValidateError: continue else: diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index f2ecaee..006469f 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -13,12 +13,12 @@ from openapi_core.schema.parameters.exceptions import ( MissingRequiredParameter, InvalidParameterValue, EmptyParameterValue, ) from openapi_core.schema.schemas.enums import SchemaType -from openapi_core.schema.schemas.exceptions import InvalidSchemaValue from openapi_core.schema.servers.exceptions import InvalidServer from openapi_core.shortcuts import ( create_spec, validate_parameters, validate_body, ) from openapi_core.testing import MockRequest, MockResponse +from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue from openapi_core.validation.request.datatypes import RequestParameters from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator @@ -182,7 +182,6 @@ class TestPetstore(object): type=SchemaType.OBJECT, value=data_json, schema_errors=original_exc.schema_errors, - schema_errors_iter=original_exc._schema_errors_iter, ), ), ] diff --git a/tests/unit/schema/test_media_types.py b/tests/unit/schema/test_media_types.py index 2d266ac..375f51d 100644 --- a/tests/unit/schema/test_media_types.py +++ b/tests/unit/schema/test_media_types.py @@ -3,6 +3,7 @@ import pytest from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue from openapi_core.schema.media_types.models import MediaType from openapi_core.schema.schemas.models import Schema +from openapi_core.unmarshalling.schemas.formatters import Formatter class TestMediaTypeCast(object): @@ -35,16 +36,20 @@ class TestParameterUnmarshal(object): media_type.unmarshal(value) def test_schema_custom_format_invalid(self): - def custom_formatter(value): - raise ValueError + + class CustomFormatter(Formatter): + def unmarshal(self, value): + raise ValueError + formatter = CustomFormatter() + custom_format = 'custom' + custom_formatters = { + custom_format: formatter, + } schema = Schema( 'string', - schema_format='custom', + schema_format=custom_format, _source={'type': 'string', 'format': 'custom'}, ) - custom_formatters = { - 'custom': custom_formatter, - } media_type = MediaType('application/json', schema=schema) value = 'test' diff --git a/tests/unit/schema/test_parameters.py b/tests/unit/schema/test_parameters.py index 2755dcb..93f5790 100644 --- a/tests/unit/schema/test_parameters.py +++ b/tests/unit/schema/test_parameters.py @@ -6,6 +6,7 @@ from openapi_core.schema.parameters.exceptions import ( from openapi_core.schema.parameters.enums import ParameterStyle from openapi_core.schema.parameters.models import Parameter from openapi_core.schema.schemas.models import Schema +from openapi_core.unmarshalling.schemas.formatters import Formatter class TestParameterInit(object): @@ -93,16 +94,20 @@ class TestParameterUnmarshal(object): param.unmarshal(value) def test_query_schema_custom_format_invalid(self): - def custom_formatter(value): - raise ValueError + + class CustomFormatter(Formatter): + def unmarshal(self, value): + raise ValueError + formatter = CustomFormatter() + custom_format = 'custom' + custom_formatters = { + custom_format: formatter, + } schema = Schema( 'string', - schema_format='custom', + schema_format=custom_format, _source={'type': 'string', 'format': 'custom'}, ) - custom_formatters = { - 'custom': custom_formatter, - } param = Parameter('param', 'query', schema=schema) value = 'test' diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index e09c788..bd31049 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -4,21 +4,16 @@ import uuid import mock import pytest -from openapi_core.extensions.models.models import Model -from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType -from openapi_core.schema.schemas.exceptions import ( - InvalidSchemaValue, OpenAPISchemaError, -) +from openapi_core.schema.schemas.enums import SchemaType from openapi_core.schema.schemas.models import Schema from openapi_core.schema.schemas.types import NoValue from openapi_core.unmarshalling.schemas.exceptions import ( - InvalidCustomFormatSchemaValue, + InvalidSchemaFormatValue, FormatterNotFoundError, UnmarshalError, - UnmarshallerStrictTypeError, + InvalidSchemaValue, ) - -from six import b, u +from openapi_core.unmarshalling.schemas.formatters import Formatter class TestSchemaIteritems(object): @@ -55,7 +50,7 @@ class TestSchemaUnmarshal(object): schema = Schema(schema_type) value = '' - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_string_valid(self): @@ -67,23 +62,22 @@ class TestSchemaUnmarshal(object): assert result == value def test_string_format_uuid_valid(self): - schema = Schema(SchemaType.STRING, schema_format=SchemaFormat.UUID) + schema = Schema(SchemaType.STRING, schema_format='uuid') value = str(uuid.uuid4()) result = schema.unmarshal(value) assert result == uuid.UUID(value) - def test_string_format_uuid_uuid_quirks_valid(self): - schema = Schema(SchemaType.STRING, schema_format=SchemaFormat.UUID) + def test_string_format_uuid_uuid_quirks_invalid(self): + schema = Schema(SchemaType.STRING, schema_format='uuid') value = uuid.uuid4() - result = schema.unmarshal(value, strict=False) - - assert result == value + with pytest.raises(InvalidSchemaValue): + schema.unmarshal(value) def test_string_format_password(self): - schema = Schema(SchemaType.STRING, schema_format=SchemaFormat.PASSWORD) + schema = Schema(SchemaType.STRING, schema_format='password') value = 'password' result = schema.unmarshal(value) @@ -94,7 +88,7 @@ class TestSchemaUnmarshal(object): schema = Schema('string') value = 1.23 - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_string_default(self): @@ -132,27 +126,39 @@ class TestSchemaUnmarshal(object): assert result == datetime.datetime(2018, 1, 2, 0, 0) def test_string_format_custom(self): - def custom_formatter(value): - return 'x-custom' + formatted = 'x-custom' + + class CustomFormatter(Formatter): + def unmarshal(self, value): + return formatted custom_format = 'custom' schema = Schema('string', schema_format=custom_format) value = 'x' + formatter = CustomFormatter() + custom_formatters = { + custom_format: formatter, + } - result = schema.unmarshal( - value, custom_formatters={custom_format: custom_formatter}) + result = schema.unmarshal(value, custom_formatters=custom_formatters) - assert result == custom_formatter(value) + assert result == formatted def test_string_format_custom_value_error(self): - def custom_formatter(value): - raise ValueError + + class CustomFormatter(Formatter): + def unmarshal(self, value): + raise ValueError custom_format = 'custom' schema = Schema('string', schema_format=custom_format) value = 'x' + formatter = CustomFormatter() + custom_formatters = { + custom_format: formatter, + } - with pytest.raises(InvalidCustomFormatSchemaValue): + with pytest.raises(InvalidSchemaFormatValue): schema.unmarshal( - value, custom_formatters={custom_format: custom_formatter}) + value, custom_formatters=custom_formatters) def test_string_format_unknown(self): unknown_format = 'unknown' @@ -187,7 +193,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer') value = '123' - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_integer_enum_invalid(self): @@ -234,7 +240,7 @@ class TestSchemaUnmarshal(object): schema = Schema('integer') value = 'abc' - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_array_valid(self): @@ -249,14 +255,14 @@ class TestSchemaUnmarshal(object): schema = Schema('array', items=Schema('string')) value = '123' - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_array_of_integer_string_invalid(self): schema = Schema('array', items=Schema('integer')) value = '123' - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_boolean_valid(self): @@ -271,7 +277,7 @@ class TestSchemaUnmarshal(object): schema = Schema('boolean') value = 'True' - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_number_valid(self): @@ -286,7 +292,7 @@ class TestSchemaUnmarshal(object): schema = Schema('number') value = '1.23' - with pytest.raises(UnmarshallerStrictTypeError): + with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) def test_number_int(self): @@ -329,737 +335,3 @@ class TestSchemaUnmarshal(object): def test_schema_any(self): schema = Schema() assert schema.unmarshal('string') == 'string' - - -class TestSchemaValidate(object): - - @pytest.mark.parametrize('schema_type', [ - 'boolean', 'array', 'integer', 'number', 'string', - ]) - def test_null(self, schema_type): - schema = Schema(schema_type) - value = None - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('schema_type', [ - 'boolean', 'array', 'integer', 'number', 'string', - ]) - def test_nullable(self, schema_type): - schema = Schema(schema_type, nullable=True) - value = None - - result = schema.validate(value) - - assert result is None - - @pytest.mark.xfail( - reason="validation does not care about custom formats atm") - def test_string_format_custom_missing(self): - custom_format = 'custom' - schema = Schema('string', schema_format=custom_format) - value = 'x' - - with pytest.raises(OpenAPISchemaError): - schema.validate(value) - - @pytest.mark.parametrize('value', [False, True]) - def test_boolean(self, value): - schema = Schema('boolean') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [1, 3.14, u('true'), [True, False]]) - def test_boolean_invalid(self, value): - schema = Schema('boolean') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [(1, 2)]) - def test_array_no_schema(self, value): - schema = Schema('array') - - with pytest.raises(OpenAPISchemaError): - schema.validate(value) - - @pytest.mark.parametrize('value', [[1, 2]]) - def test_array(self, value): - schema = Schema('array', items=Schema('integer')) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [False, 1, 3.14, u('true'), (3, 4)]) - def test_array_invalid(self, value): - schema = Schema('array') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [1, 3]) - def test_integer(self, value): - schema = Schema('integer') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [False, 3.14, u('true'), [1, 2]]) - def test_integer_invalid(self, value): - schema = Schema('integer') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [0, 1, 2]) - def test_integer_minimum_invalid(self, value): - schema = Schema('integer', minimum=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [4, 5, 6]) - def test_integer_minimum(self, value): - schema = Schema('integer', minimum=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [4, 5, 6]) - def test_integer_maximum_invalid(self, value): - schema = Schema('integer', maximum=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [0, 1, 2]) - def test_integer_maximum(self, value): - schema = Schema('integer', maximum=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [1, 2, 4]) - def test_integer_multiple_of_invalid(self, value): - schema = Schema('integer', multiple_of=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [3, 6, 18]) - def test_integer_multiple_of(self, value): - schema = Schema('integer', multiple_of=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [1, 3.14]) - def test_number(self, value): - schema = Schema('number') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [False, 'true', [1, 3]]) - def test_number_invalid(self, value): - schema = Schema('number') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [0, 1, 2]) - def test_number_minimum_invalid(self, value): - schema = Schema('number', minimum=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [3, 4, 5]) - def test_number_minimum(self, value): - schema = Schema('number', minimum=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [1, 2, 3]) - def test_number_exclusive_minimum_invalid(self, value): - schema = Schema('number', minimum=3, exclusive_minimum=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [4, 5, 6]) - def test_number_exclusive_minimum(self, value): - schema = Schema('number', minimum=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [4, 5, 6]) - def test_number_maximum_invalid(self, value): - schema = Schema('number', maximum=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [1, 2, 3]) - def test_number_maximum(self, value): - schema = Schema('number', maximum=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [3, 4, 5]) - def test_number_exclusive_maximum_invalid(self, value): - schema = Schema('number', maximum=3, exclusive_maximum=True) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [0, 1, 2]) - def test_number_exclusive_maximum(self, value): - schema = Schema('number', maximum=3, exclusive_maximum=True) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [1, 2, 4]) - def test_number_multiple_of_invalid(self, value): - schema = Schema('number', multiple_of=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [3, 6, 18]) - def test_number_multiple_of(self, value): - schema = Schema('number', multiple_of=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [u('true'), b('test')]) - def test_string(self, value): - schema = Schema('string') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [False, 1, 3.14, [1, 3]]) - def test_string_invalid(self, value): - schema = Schema('string') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - b('true'), u('test'), False, 1, 3.14, [1, 3], - datetime.datetime(1989, 1, 2), - ]) - def test_string_format_date_invalid(self, value): - schema = Schema('string', schema_format='date') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - u('1989-01-02'), u('2018-01-02'), - ]) - def test_string_format_date(self, value): - schema = Schema('string', schema_format='date') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [ - u('12345678-1234-5678-1234-567812345678'), - ]) - def test_string_format_uuid(self, value): - schema = Schema('string', schema_format='uuid') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [ - b('true'), u('true'), False, 1, 3.14, [1, 3], - datetime.date(2018, 1, 2), datetime.datetime(2018, 1, 2, 23, 59, 59), - ]) - def test_string_format_uuid_invalid(self, value): - schema = Schema('string', schema_format='uuid') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - b('true'), u('true'), False, 1, 3.14, [1, 3], - u('1989-01-02'), - ]) - def test_string_format_datetime_invalid(self, value): - schema = Schema('string', schema_format='date-time') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - u('1989-01-02T00:00:00Z'), - u('2018-01-02T23:59:59Z'), - ]) - @mock.patch( - 'openapi_core.schema.schemas._format.' - 'DATETIME_HAS_STRICT_RFC3339', True - ) - @mock.patch( - 'openapi_core.schema.schemas._format.' - 'DATETIME_HAS_ISODATE', False - ) - def test_string_format_datetime_strict_rfc3339(self, value): - schema = Schema('string', schema_format='date-time') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [ - u('1989-01-02T00:00:00Z'), - u('2018-01-02T23:59:59Z'), - ]) - @mock.patch( - 'openapi_core.schema.schemas._format.' - 'DATETIME_HAS_STRICT_RFC3339', False - ) - @mock.patch( - 'openapi_core.schema.schemas._format.' - 'DATETIME_HAS_ISODATE', True - ) - def test_string_format_datetime_isodate(self, value): - schema = Schema('string', schema_format='date-time') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [ - u('true'), False, 1, 3.14, [1, 3], u('1989-01-02'), - u('1989-01-02T00:00:00Z'), - ]) - def test_string_format_binary_invalid(self, value): - schema = Schema('string', schema_format='binary') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - b('stream'), b('text'), - ]) - def test_string_format_binary(self, value): - schema = Schema('string', schema_format='binary') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [ - b('dGVzdA=='), u('dGVzdA=='), - ]) - def test_string_format_byte(self, value): - schema = Schema('string', schema_format='byte') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [ - u('tsssst'), b('tsssst'), b('tesddddsdsdst'), - ]) - def test_string_format_byte_invalid(self, value): - schema = Schema('string', schema_format='byte') - - with pytest.raises(OpenAPISchemaError): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - u('test'), b('stream'), datetime.date(1989, 1, 2), - datetime.datetime(1989, 1, 2, 0, 0, 0), - ]) - def test_string_format_unknown(self, value): - unknown_format = 'unknown' - schema = Schema('string', schema_format=unknown_format) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [u(""), u("a"), u("ab")]) - def test_string_min_length_invalid(self, value): - schema = Schema('string', min_length=3) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [u("abc"), u("abcd")]) - def test_string_min_length(self, value): - schema = Schema('string', min_length=3) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [u(""), ]) - def test_string_max_length_invalid_schema(self, value): - schema = Schema('string', max_length=-1) - - with pytest.raises(OpenAPISchemaError): - schema.validate(value) - - @pytest.mark.parametrize('value', [u("ab"), u("abc")]) - def test_string_max_length_invalid(self, value): - schema = Schema('string', max_length=1) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [u(""), u("a")]) - def test_string_max_length(self, value): - schema = Schema('string', max_length=1) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [u("foo"), u("bar")]) - def test_string_pattern_invalid(self, value): - schema = Schema('string', pattern='baz') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [u("bar"), u("foobar")]) - def test_string_pattern(self, value): - schema = Schema('string', pattern='bar') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', ['true', False, 1, 3.14, [1, 3]]) - def test_object_not_an_object(self, value): - schema = Schema('object') - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [Model(), ]) - def test_object_multiple_one_of(self, value): - one_of = [ - Schema('object'), Schema('object'), - ] - schema = Schema('object', one_of=one_of) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [{}, ]) - def test_object_defferent_type_one_of(self, value): - one_of = [ - Schema('integer'), Schema('string'), - ] - schema = Schema('object', one_of=one_of) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [{}, ]) - def test_object_no_one_of(self, value): - one_of = [ - Schema( - 'object', - properties={'test1': Schema('string')}, - required=['test1', ], - ), - Schema( - 'object', - properties={'test2': Schema('string')}, - required=['test2', ], - ), - ] - schema = Schema('object', one_of=one_of) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - { - 'foo': u("FOO"), - }, - { - 'foo': u("FOO"), - 'bar': u("BAR"), - }, - ]) - def test_unambiguous_one_of(self, value): - one_of = [ - Schema( - 'object', - properties={ - 'foo': Schema('string'), - }, - additional_properties=False, - required=['foo'], - ), - Schema( - 'object', - properties={ - 'foo': Schema('string'), - 'bar': Schema('string'), - }, - additional_properties=False, - required=['foo', 'bar'], - ), - ] - schema = Schema('object', one_of=one_of) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [{}, ]) - def test_object_default_property(self, value): - schema = Schema('object', default='value1') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [Model(), ]) - def test_object_min_properties_invalid_schema(self, value): - schema = Schema('object', min_properties=-1) - - with pytest.raises(OpenAPISchemaError): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - {'a': 1}, - {'a': 1, 'b': 2}, - {'a': 1, 'b': 2, 'c': 3}, - ]) - def test_object_min_properties_invalid(self, value): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - min_properties=4, - ) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - {'a': 1}, - {'a': 1, 'b': 2}, - {'a': 1, 'b': 2, 'c': 3}, - ]) - def test_object_min_properties(self, value): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - min_properties=1, - ) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [Model(), ]) - def test_object_max_properties_invalid_schema(self, value): - schema = Schema('object', max_properties=-1) - - with pytest.raises(OpenAPISchemaError): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - {'a': 1}, - {'a': 1, 'b': 2}, - {'a': 1, 'b': 2, 'c': 3}, - ]) - def test_object_max_properties_invalid(self, value): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - max_properties=0, - ) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - {'a': 1}, - {'a': 1, 'b': 2}, - {'a': 1, 'b': 2, 'c': 3}, - ]) - def test_object_max_properties(self, value): - schema = Schema( - 'object', - properties={k: Schema('number') - for k in ['a', 'b', 'c']}, - max_properties=3, - ) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [{'additional': 1}, ]) - def test_object_additional_propetries(self, value): - schema = Schema('object') - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [{'additional': 1}, ]) - def test_object_additional_propetries_false(self, value): - schema = Schema('object', additional_properties=False) - - with pytest.raises(InvalidSchemaValue): - schema.validate(value) - - @pytest.mark.parametrize('value', [{'additional': 1}, ]) - def test_object_additional_propetries_object(self, value): - additional_properties = Schema('integer') - schema = Schema('object', additional_properties=additional_properties) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [[], [1], [1, 2]]) - def test_list_min_items_invalid(self, value): - schema = Schema( - 'array', - items=Schema('number'), - min_items=3, - ) - - with pytest.raises(Exception): - schema.validate(value) - - @pytest.mark.parametrize('value', [[], [1], [1, 2]]) - def test_list_min_items(self, value): - schema = Schema( - 'array', - items=Schema('number'), - min_items=0, - ) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [[], ]) - def test_list_max_items_invalid_schema(self, value): - schema = Schema( - 'array', - items=Schema('number'), - max_items=-1, - ) - - with pytest.raises(OpenAPISchemaError): - schema.validate(value) - - @pytest.mark.parametrize('value', [[1, 2], [2, 3, 4]]) - def test_list_max_items_invalid(self, value): - schema = Schema( - 'array', - items=Schema('number'), - max_items=1, - ) - - with pytest.raises(Exception): - schema.validate(value) - - @pytest.mark.parametrize('value', [[1, 2, 1], [2, 2]]) - def test_list_unique_items_invalid(self, value): - schema = Schema( - 'array', - items=Schema('number'), - unique_items=True, - ) - - with pytest.raises(Exception): - schema.validate(value) - - @pytest.mark.parametrize('value', [ - { - 'someint': 123, - }, - { - 'somestr': u('content'), - }, - { - 'somestr': u('content'), - 'someint': 123, - }, - ]) - def test_object_with_properties(self, value): - schema = Schema( - 'object', - properties={ - 'somestr': Schema('string'), - 'someint': Schema('integer'), - }, - ) - - result = schema.validate(value) - - assert result is None - - @pytest.mark.parametrize('value', [ - { - 'somestr': {}, - 'someint': 123, - }, - { - 'somestr': [ - 'content1', 'content2' - ], - 'someint': 123, - }, - { - 'somestr': 123, - 'someint': 123, - }, - { - 'somestr': 'content', - 'someint': 123, - 'not_in_scheme_prop': 123, - }, - ]) - def test_object_with_invalid_properties(self, value): - schema = Schema( - 'object', - properties={ - 'somestr': Schema('string'), - 'someint': Schema('integer'), - }, - additional_properties=False, - ) - - with pytest.raises(Exception): - schema.validate(value) diff --git a/tests/unit/unmarshalling/test_validate.py b/tests/unit/unmarshalling/test_validate.py new file mode 100644 index 0000000..22cedf9 --- /dev/null +++ b/tests/unit/unmarshalling/test_validate.py @@ -0,0 +1,761 @@ +import datetime + +import mock +import pytest + +from openapi_core.extensions.models.models import Model +from openapi_core.schema.schemas.exceptions import OpenAPISchemaError +from openapi_core.schema.schemas.models import Schema +from openapi_core.unmarshalling.schemas.factories import ( + SchemaUnmarshallersFactory, +) +from openapi_core.unmarshalling.schemas.exceptions import ( + FormatterNotFoundError, InvalidSchemaValue, +) + +from six import b, u + + +class TestSchemaValidate(object): + + @pytest.fixture + def validator_factory(self): + def create_validator(schema): + return SchemaUnmarshallersFactory().create(schema) + return create_validator + + @pytest.mark.parametrize('schema_type', [ + 'boolean', 'array', 'integer', 'number', 'string', + ]) + def test_null(self, schema_type, validator_factory): + schema = Schema(schema_type) + value = None + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('schema_type', [ + 'boolean', 'array', 'integer', 'number', 'string', + ]) + def test_nullable(self, schema_type, validator_factory): + schema = Schema(schema_type, nullable=True) + value = None + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.xfail( + reason="validation does not care about custom formats atm") + def test_string_format_custom_missing(self, validator_factory): + custom_format = 'custom' + schema = Schema('string', schema_format=custom_format) + value = 'x' + + with pytest.raises(OpenAPISchemaError): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [False, True]) + def test_boolean(self, value, validator_factory): + schema = Schema('boolean') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [1, 3.14, u('true'), [True, False]]) + def test_boolean_invalid(self, value, validator_factory): + schema = Schema('boolean') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [(1, 2)]) + def test_array_no_schema(self, value, validator_factory): + schema = Schema('array') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [[1, 2]]) + def test_array(self, value, validator_factory): + schema = Schema('array', items=Schema('integer')) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [False, 1, 3.14, u('true'), (3, 4)]) + def test_array_invalid(self, value, validator_factory): + schema = Schema('array') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [1, 3]) + def test_integer(self, value, validator_factory): + schema = Schema('integer') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [False, 3.14, u('true'), [1, 2]]) + def test_integer_invalid(self, value, validator_factory): + schema = Schema('integer') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [0, 1, 2]) + def test_integer_minimum_invalid(self, value, validator_factory): + schema = Schema('integer', minimum=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [4, 5, 6]) + def test_integer_minimum(self, value, validator_factory): + schema = Schema('integer', minimum=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [4, 5, 6]) + def test_integer_maximum_invalid(self, value, validator_factory): + schema = Schema('integer', maximum=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [0, 1, 2]) + def test_integer_maximum(self, value, validator_factory): + schema = Schema('integer', maximum=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [1, 2, 4]) + def test_integer_multiple_of_invalid(self, value, validator_factory): + schema = Schema('integer', multiple_of=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [3, 6, 18]) + def test_integer_multiple_of(self, value, validator_factory): + schema = Schema('integer', multiple_of=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [1, 3.14]) + def test_number(self, value, validator_factory): + schema = Schema('number') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [False, 'true', [1, 3]]) + def test_number_invalid(self, value, validator_factory): + schema = Schema('number') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [0, 1, 2]) + def test_number_minimum_invalid(self, value, validator_factory): + schema = Schema('number', minimum=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [3, 4, 5]) + def test_number_minimum(self, value, validator_factory): + schema = Schema('number', minimum=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [1, 2, 3]) + def test_number_exclusive_minimum_invalid(self, value, validator_factory): + schema = Schema('number', minimum=3, exclusive_minimum=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [4, 5, 6]) + def test_number_exclusive_minimum(self, value, validator_factory): + schema = Schema('number', minimum=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [4, 5, 6]) + def test_number_maximum_invalid(self, value, validator_factory): + schema = Schema('number', maximum=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [1, 2, 3]) + def test_number_maximum(self, value, validator_factory): + schema = Schema('number', maximum=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [3, 4, 5]) + def test_number_exclusive_maximum_invalid(self, value, validator_factory): + schema = Schema('number', maximum=3, exclusive_maximum=True) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [0, 1, 2]) + def test_number_exclusive_maximum(self, value, validator_factory): + schema = Schema('number', maximum=3, exclusive_maximum=True) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [1, 2, 4]) + def test_number_multiple_of_invalid(self, value, validator_factory): + schema = Schema('number', multiple_of=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [3, 6, 18]) + def test_number_multiple_of(self, value, validator_factory): + schema = Schema('number', multiple_of=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [u('true'), b('test')]) + def test_string(self, value, validator_factory): + schema = Schema('string') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [False, 1, 3.14, [1, 3]]) + def test_string_invalid(self, value, validator_factory): + schema = Schema('string') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + b('true'), u('test'), False, 1, 3.14, [1, 3], + datetime.datetime(1989, 1, 2), + ]) + def test_string_format_date_invalid(self, value, validator_factory): + schema = Schema('string', schema_format='date') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + u('1989-01-02'), u('2018-01-02'), + ]) + def test_string_format_date(self, value, validator_factory): + schema = Schema('string', schema_format='date') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [ + u('12345678-1234-5678-1234-567812345678'), + ]) + def test_string_format_uuid(self, value, validator_factory): + schema = Schema('string', schema_format='uuid') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [ + b('true'), u('true'), False, 1, 3.14, [1, 3], + datetime.date(2018, 1, 2), datetime.datetime(2018, 1, 2, 23, 59, 59), + ]) + def test_string_format_uuid_invalid(self, value, validator_factory): + schema = Schema('string', schema_format='uuid') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + b('true'), u('true'), False, 1, 3.14, [1, 3], + u('1989-01-02'), + ]) + def test_string_format_datetime_invalid(self, value, validator_factory): + schema = Schema('string', schema_format='date-time') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + u('1989-01-02T00:00:00Z'), + u('2018-01-02T23:59:59Z'), + ]) + @mock.patch( + 'openapi_core.schema_validator._format.' + 'DATETIME_HAS_STRICT_RFC3339', True + ) + @mock.patch( + 'openapi_core.schema_validator._format.' + 'DATETIME_HAS_ISODATE', False + ) + def test_string_format_datetime_strict_rfc3339( + self, value, validator_factory): + schema = Schema('string', schema_format='date-time') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [ + u('1989-01-02T00:00:00Z'), + u('2018-01-02T23:59:59Z'), + ]) + @mock.patch( + 'openapi_core.schema_validator._format.' + 'DATETIME_HAS_STRICT_RFC3339', False + ) + @mock.patch( + 'openapi_core.schema_validator._format.' + 'DATETIME_HAS_ISODATE', True + ) + def test_string_format_datetime_isodate(self, value, validator_factory): + schema = Schema('string', schema_format='date-time') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [ + u('true'), False, 1, 3.14, [1, 3], u('1989-01-02'), + u('1989-01-02T00:00:00Z'), + ]) + def test_string_format_binary_invalid(self, value, validator_factory): + schema = Schema('string', schema_format='binary') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + b('stream'), b('text'), + ]) + def test_string_format_binary(self, value, validator_factory): + schema = Schema('string', schema_format='binary') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [ + b('dGVzdA=='), u('dGVzdA=='), + ]) + def test_string_format_byte(self, value, validator_factory): + schema = Schema('string', schema_format='byte') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [ + u('tsssst'), b('tsssst'), b('tesddddsdsdst'), + ]) + def test_string_format_byte_invalid(self, value, validator_factory): + schema = Schema('string', schema_format='byte') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + u('test'), b('stream'), datetime.date(1989, 1, 2), + datetime.datetime(1989, 1, 2, 0, 0, 0), + ]) + def test_string_format_unknown(self, value, validator_factory): + unknown_format = 'unknown' + schema = Schema('string', schema_format=unknown_format) + + with pytest.raises(FormatterNotFoundError): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [u(""), u("a"), u("ab")]) + def test_string_min_length_invalid(self, value, validator_factory): + schema = Schema('string', min_length=3) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [u("abc"), u("abcd")]) + def test_string_min_length(self, value, validator_factory): + schema = Schema('string', min_length=3) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [u(""), ]) + def test_string_max_length_invalid_schema(self, value, validator_factory): + schema = Schema('string', max_length=-1) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [u("ab"), u("abc")]) + def test_string_max_length_invalid(self, value, validator_factory): + schema = Schema('string', max_length=1) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [u(""), u("a")]) + def test_string_max_length(self, value, validator_factory): + schema = Schema('string', max_length=1) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [u("foo"), u("bar")]) + def test_string_pattern_invalid(self, value, validator_factory): + schema = Schema('string', pattern='baz') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [u("bar"), u("foobar")]) + def test_string_pattern(self, value, validator_factory): + schema = Schema('string', pattern='bar') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', ['true', False, 1, 3.14, [1, 3]]) + def test_object_not_an_object(self, value, validator_factory): + schema = Schema('object') + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [Model(), ]) + def test_object_multiple_one_of(self, value, validator_factory): + one_of = [ + Schema('object'), Schema('object'), + ] + schema = Schema('object', one_of=one_of) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [{}, ]) + def test_object_defferent_type_one_of(self, value, validator_factory): + one_of = [ + Schema('integer'), Schema('string'), + ] + schema = Schema('object', one_of=one_of) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [{}, ]) + def test_object_no_one_of(self, value, validator_factory): + one_of = [ + Schema( + 'object', + properties={'test1': Schema('string')}, + required=['test1', ], + ), + Schema( + 'object', + properties={'test2': Schema('string')}, + required=['test2', ], + ), + ] + schema = Schema('object', one_of=one_of) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + { + 'foo': u("FOO"), + }, + { + 'foo': u("FOO"), + 'bar': u("BAR"), + }, + ]) + def test_unambiguous_one_of(self, value, validator_factory): + one_of = [ + Schema( + 'object', + properties={ + 'foo': Schema('string'), + }, + additional_properties=False, + required=['foo'], + ), + Schema( + 'object', + properties={ + 'foo': Schema('string'), + 'bar': Schema('string'), + }, + additional_properties=False, + required=['foo', 'bar'], + ), + ] + schema = Schema('object', one_of=one_of) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [{}, ]) + def test_object_default_property(self, value, validator_factory): + schema = Schema('object', default='value1') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [{}, ]) + def test_object_min_properties_invalid_schema( + self, value, validator_factory): + schema = Schema('object', min_properties=2) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + {'a': 1}, + {'a': 1, 'b': 2}, + {'a': 1, 'b': 2, 'c': 3}, + ]) + def test_object_min_properties_invalid(self, value, validator_factory): + schema = Schema( + 'object', + properties={k: Schema('number') + for k in ['a', 'b', 'c']}, + min_properties=4, + ) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + {'a': 1}, + {'a': 1, 'b': 2}, + {'a': 1, 'b': 2, 'c': 3}, + ]) + def test_object_min_properties(self, value, validator_factory): + schema = Schema( + 'object', + properties={k: Schema('number') + for k in ['a', 'b', 'c']}, + min_properties=1, + ) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [{}, ]) + def test_object_max_properties_invalid_schema( + self, value, validator_factory): + schema = Schema('object', max_properties=-1) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + {'a': 1}, + {'a': 1, 'b': 2}, + {'a': 1, 'b': 2, 'c': 3}, + ]) + def test_object_max_properties_invalid(self, value, validator_factory): + schema = Schema( + 'object', + properties={k: Schema('number') + for k in ['a', 'b', 'c']}, + max_properties=0, + ) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + {'a': 1}, + {'a': 1, 'b': 2}, + {'a': 1, 'b': 2, 'c': 3}, + ]) + def test_object_max_properties(self, value, validator_factory): + schema = Schema( + 'object', + properties={k: Schema('number') + for k in ['a', 'b', 'c']}, + max_properties=3, + ) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [{'additional': 1}, ]) + def test_object_additional_propetries(self, value, validator_factory): + schema = Schema('object') + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [{'additional': 1}, ]) + def test_object_additional_propetries_false( + self, value, validator_factory): + schema = Schema('object', additional_properties=False) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [{'additional': 1}, ]) + def test_object_additional_propetries_object( + self, value, validator_factory): + additional_properties = Schema('integer') + schema = Schema('object', additional_properties=additional_properties) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [[], [1], [1, 2]]) + def test_list_min_items_invalid(self, value, validator_factory): + schema = Schema( + 'array', + items=Schema('number'), + min_items=3, + ) + + with pytest.raises(Exception): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [[], [1], [1, 2]]) + def test_list_min_items(self, value, validator_factory): + schema = Schema( + 'array', + items=Schema('number'), + min_items=0, + ) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [[], ]) + def test_list_max_items_invalid_schema(self, value, validator_factory): + schema = Schema( + 'array', + items=Schema('number'), + max_items=-1, + ) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [[1, 2], [2, 3, 4]]) + def test_list_max_items_invalid(self, value, validator_factory): + schema = Schema( + 'array', + items=Schema('number'), + max_items=1, + ) + + with pytest.raises(Exception): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [[1, 2, 1], [2, 2]]) + def test_list_unique_items_invalid(self, value, validator_factory): + schema = Schema( + 'array', + items=Schema('number'), + unique_items=True, + ) + + with pytest.raises(Exception): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [ + { + 'someint': 123, + }, + { + 'somestr': u('content'), + }, + { + 'somestr': u('content'), + 'someint': 123, + }, + ]) + def test_object_with_properties(self, value, validator_factory): + schema = Schema( + 'object', + properties={ + 'somestr': Schema('string'), + 'someint': Schema('integer'), + }, + ) + + result = validator_factory(schema).validate(value) + + assert result is None + + @pytest.mark.parametrize('value', [ + { + 'somestr': {}, + 'someint': 123, + }, + { + 'somestr': [ + 'content1', 'content2' + ], + 'someint': 123, + }, + { + 'somestr': 123, + 'someint': 123, + }, + { + 'somestr': 'content', + 'someint': 123, + 'not_in_scheme_prop': 123, + }, + ]) + def test_object_with_invalid_properties(self, value, validator_factory): + schema = Schema( + 'object', + properties={ + 'somestr': Schema('string'), + 'someint': Schema('integer'), + }, + additional_properties=False, + ) + + with pytest.raises(Exception): + validator_factory(schema).validate(value)