diff --git a/openapi_core/schema/schemas/factories.py b/openapi_core/schema/schemas/factories.py index 55b48fe1..0ffb02eb 100644 --- a/openapi_core/schema/schemas/factories.py +++ b/openapi_core/schema/schemas/factories.py @@ -31,6 +31,7 @@ def create(self, schema_spec): deprecated = schema_deref.get('deprecated', False) all_of_spec = schema_deref.get('allOf', None) one_of_spec = schema_deref.get('oneOf', None) + any_of_spec = schema_deref.get('anyOf', None) additional_properties_spec = schema_deref.get('additionalProperties', True) min_items = schema_deref.get('minItems', None) @@ -63,6 +64,10 @@ def create(self, schema_spec): if one_of_spec: one_of = list(map(self.create, one_of_spec)) + any_of = [] + if any_of_spec: + any_of = list(map(self.create, any_of_spec)) + items = None if items_spec: items = self._create_items(items_spec) @@ -75,7 +80,7 @@ def create(self, schema_spec): schema_type=schema_type, properties=properties, items=items, schema_format=schema_format, required=required, default=default, nullable=nullable, enum=enum, - deprecated=deprecated, all_of=all_of, one_of=one_of, + deprecated=deprecated, all_of=all_of, one_of=one_of, any_of=any_of, additional_properties=additional_properties, min_items=min_items, max_items=max_items, min_length=min_length, max_length=max_length, pattern=pattern, unique_items=unique_items, @@ -118,6 +123,10 @@ class SchemaDictFactory(object): 'one_of', dest_prop_name='oneOf', is_list=True, dest_default=[], ), + Contribution( + 'any_of', + dest_prop_name='anyOf', is_list=True, dest_default=[], + ), Contribution( 'additional_properties', dest_prop_name='additionalProperties', dest_default=True, diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index a4109c4d..4a187ae4 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -21,7 +21,7 @@ class Schema(object): def __init__( self, schema_type=None, properties=None, items=None, schema_format=None, required=None, default=NoValue, nullable=False, - enum=None, deprecated=False, all_of=None, one_of=None, + enum=None, deprecated=False, all_of=None, one_of=None, any_of=None, additional_properties=True, min_items=None, max_items=None, min_length=None, max_length=None, pattern=None, unique_items=False, minimum=None, maximum=None, multiple_of=None, @@ -40,6 +40,7 @@ def __init__( self.deprecated = deprecated self.all_of = all_of and list(all_of) or [] self.one_of = one_of and list(one_of) or [] + self.any_of = any_of and list(any_of) or [] self.additional_properties = additional_properties self.min_items = int(min_items) if min_items is not None else None self.max_items = int(max_items) if max_items is not None else None diff --git a/openapi_core/unmarshalling/schemas/unmarshallers.py b/openapi_core/unmarshalling/schemas/unmarshallers.py index ef0fdb70..19c6a66b 100644 --- a/openapi_core/unmarshalling/schemas/unmarshallers.py +++ b/openapi_core/unmarshalling/schemas/unmarshallers.py @@ -187,6 +187,23 @@ def _unmarshal_object(self, value=NoValue): if properties is None: log.warning("valid oneOf schema not found") + if self.schema.any_of: + properties = None + for any_of_schema in self.schema.any_of: + try: + unmarshalled = self._unmarshal_properties( + value, any_of_schema) + except (UnmarshalError, ValueError): + pass + else: + if properties is not None: + log.warning("multiple valid anyOf schemas found") + continue + properties = unmarshalled + + if properties is None: + log.warning("valid anyOf schema not found") + else: properties = self._unmarshal_properties(value) @@ -196,7 +213,8 @@ def _unmarshal_object(self, value=NoValue): return properties - def _unmarshal_properties(self, value=NoValue, one_of_schema=None): + def _unmarshal_properties(self, value=NoValue, one_of_schema=None, + any_of_schema=None): all_props = self.schema.get_all_properties() all_props_names = self.schema.get_all_properties_names() @@ -205,6 +223,11 @@ def _unmarshal_properties(self, value=NoValue, one_of_schema=None): all_props_names |= one_of_schema.\ get_all_properties_names() + if any_of_schema is not None: + all_props.update(any_of_schema.get_all_properties()) + all_props_names |= any_of_schema.\ + get_all_properties_names() + value_props_names = value.keys() extra_props = set(value_props_names) - set(all_props_names) @@ -253,6 +276,10 @@ def __call__(self, value=NoValue): if one_of_schema: return self.unmarshallers_factory.create(one_of_schema)(value) + any_of_schema = self._get_any_of_schema(value) + if any_of_schema: + return self.unmarshallers_factory.create(any_of_schema)(value) + all_of_schema = self._get_all_of_schema(value) if all_of_schema: return self.unmarshallers_factory.create(all_of_schema)(value) @@ -283,6 +310,18 @@ def _get_one_of_schema(self, value): else: return subschema + def _get_any_of_schema(self, value): + if not self.schema.any_of: + return + for subschema in self.schema.any_of: + unmarshaller = self.unmarshallers_factory.create(subschema) + try: + unmarshaller.validate(value) + except ValidateError: + continue + else: + return subschema + def _get_all_of_schema(self, value): if not self.schema.all_of: return diff --git a/tests/unit/unmarshalling/test_validate.py b/tests/unit/unmarshalling/test_validate.py index fdb5d950..9dafb6a7 100644 --- a/tests/unit/unmarshalling/test_validate.py +++ b/tests/unit/unmarshalling/test_validate.py @@ -529,6 +529,80 @@ def test_unambiguous_one_of(self, value, validator_factory): assert result is None + @pytest.mark.parametrize('value', [Model(), ]) + def test_object_multiple_any_of(self, value, validator_factory): + any_of = [ + Schema('object'), Schema('object'), + ] + schema = Schema('object', any_of=any_of) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [{}, ]) + def test_object_different_type_any_of(self, value, validator_factory): + any_of = [ + Schema('integer'), Schema('string'), + ] + schema = Schema('object', any_of=any_of) + + with pytest.raises(InvalidSchemaValue): + validator_factory(schema).validate(value) + + @pytest.mark.parametrize('value', [{}, ]) + def test_object_no_any_of(self, value, validator_factory): + any_of = [ + Schema( + 'object', + properties={'test1': Schema('string')}, + required=['test1', ], + ), + Schema( + 'object', + properties={'test2': Schema('string')}, + required=['test2', ], + ), + ] + schema = Schema('object', any_of=any_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_any_of(self, value, validator_factory): + any_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', any_of=any_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')
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: