Skip to content

Commit

Permalink
Support for oneOf composition
Browse files Browse the repository at this point in the history
  • Loading branch information
Aniruddha Maru committed Dec 9, 2016
1 parent 2d8b48f commit d493918
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 3 deletions.
12 changes: 12 additions & 0 deletions flex/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,15 @@ def dereference_reference(reference, context):
MESSAGES['reference']['unsupported'].format(reference),
)
return jsonpointer.resolve_pointer(context, parts.fragment)


def exactly_one(iterable):
"""
Returns True if bool(x) is True for exactly one x in the iterable
If the iterable is empty, returns False
"""
it = iter(iterable)
# check that one is True, then check that none is True in rest of the
# iterator
return any(it) and not any(it)

11 changes: 8 additions & 3 deletions flex/validation/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
chain_reduce_partial,
cast_value_to_type,
deep_equal,
exactly_one
)
from flex.paths import (
match_path_to_api_path,
Expand Down Expand Up @@ -299,7 +300,7 @@ def generate_enum_validator(enum, **kwargs):


@skip_if_empty
def validate_allof_anyof(value, sub_schemas, context, method, **kwargs):
def validate_allof_anyof_oneof(value, sub_schemas, context, method, **kwargs):
from flex.validation.schema import (
construct_schema_validators,
)
Expand All @@ -323,11 +324,15 @@ def validate_allof_anyof(value, sub_schemas, context, method, **kwargs):


def generate_allof_validator(allOf, context, **kwargs):
return functools.partial(validate_allof_anyof, sub_schemas=allOf, context=context, method=all)
return functools.partial(validate_allof_anyof_oneof, sub_schemas=allOf, context=context, method=all)


def generate_anyof_validator(anyOf, context, **kwargs):
return functools.partial(validate_allof_anyof, sub_schemas=anyOf, context=context, method=any)
return functools.partial(validate_allof_anyof_oneof, sub_schemas=anyOf, context=context, method=any)


def generate_oneof_validator(oneOf, context, **kwargs):
return functools.partial(validate_allof_anyof_oneof, sub_schemas=oneOf, context=context, method=exactly_one)


def validate_object(obj, field_validators=None, non_field_validators=None,
Expand Down
2 changes: 2 additions & 0 deletions flex/validation/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
generate_object_validator,
generate_allof_validator,
generate_anyof_validator,
generate_oneof_validator,
)
from flex.datastructures import (
ValidationDict,
Expand Down Expand Up @@ -193,6 +194,7 @@ def generate_additional_properties_validator(additionalProperties, properties, *
'items': generate_items_validator,
'allOf': generate_allof_validator,
'anyOf': generate_anyof_validator,
'oneOf': generate_oneof_validator,
}


Expand Down
56 changes: 56 additions & 0 deletions tests/validation/schema/test_oneof_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

import pytest

from flex.exceptions import ValidationError
from flex.constants import (
STRING,
INTEGER,
)
from flex.error_messages import MULTIPLE_OF_MESSAGES

from tests.utils import (
generate_validator_from_schema,
assert_message_in_errors
)


def test_oneof_simple():
# example taken from http://spacetelescope.github.io/understanding-json-schema/reference/combining.html
schema = {
"oneOf": [
{ "type": INTEGER, "multipleOf": 5 },
{ "type": INTEGER, "multipleOf": 3 },
]
}
validator = generate_validator_from_schema(schema)

validator(10)
validator(9)


def test_one_of_complex_failure():
# example taken from http://spacetelescope.github.io/understanding-json-schema/reference/combining.html
schema = {
"oneOf": [
{ "type": INTEGER, "multipleOf": 5 },
{ "type": INTEGER, "multipleOf": 3 },
]
}
validator = generate_validator_from_schema(schema)

with pytest.raises(ValidationError) as err:
validator(2)

assert_message_in_errors(
MULTIPLE_OF_MESSAGES['invalid'],
err.value.detail,
)

with pytest.raises(ValidationError) as err:
validator(15)

assert_message_in_errors(
MULTIPLE_OF_MESSAGES['invalid'],
err.value.detail,
)

0 comments on commit d493918

Please sign in to comment.