Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Fixed comparing values for enum and const validations
* Fixed resolving plain-name ref fragments
* Fixed not throwing when unknown format is used
* Fixed compilation of if/then/else and propertyNames when if schema generates no validation code
* Fixed compilation of not, if/then/else, items, additionalItems, propertyNames and schema dependencies when a subschema generates no validation code
* Added option to get all the errors (set `fast_fail` to `False`)
* Added basic type hints
* Added support of duration and uuid formats from draft-2019
Expand Down
16 changes: 12 additions & 4 deletions fastjsonschema/draft04.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,10 @@ def generate_not(self):
self.exc('{name} must NOT match a disallowed definition', rule='not')
else:
with self.l('try:', optimize=False):
code_len = len(self._code)
self.generate_func_code_block(not_definition, self._variable, self._variable_name)
if len(self._code) == code_len:
self.l('pass')
self.l('except JsonSchemaValueException: pass')
with self.l('else:'):
self.exc('{name} must NOT match a disallowed definition', rule='not')
Expand Down Expand Up @@ -464,22 +467,24 @@ def generate_items(self):
self.exc('{name} must contain only specified items', rule='items')
else:
with self.l('for {variable}_x, {variable}_item in enumerate({variable}[{0}:], {0}):', len(items_definition)):
count = self.generate_func_code_block(
code_len = len(self._code)
self.generate_func_code_block(
self._definition['additionalItems'],
'{}_item'.format(self._variable),
'{}[{{{}_x}}]'.format(self._variable_name, self._variable),
)
if not count:
if len(self._code) == code_len:
self.l('pass')
else:
if items_definition:
with self.l('for {variable}_x, {variable}_item in enumerate({variable}):'):
count = self.generate_func_code_block(
code_len = len(self._code)
self.generate_func_code_block(
items_definition,
'{}_item'.format(self._variable),
'{}[{{{}_x}}]'.format(self._variable_name, self._variable),
)
if not count:
if len(self._code) == code_len:
self.l('pass')

def generate_min_properties(self):
Expand Down Expand Up @@ -658,6 +663,9 @@ def generate_dependencies(self):
with self.l('if "{}" not in {variable}:', self.e(value)):
self.exc('{name} missing dependency {} for {}', self.e(value), self.e(key), rule='dependencies')
else:
code_len = len(self._code)
self.generate_func_code_block(values, self._variable, self._variable_name, clear_variables=True)
if len(self._code) == code_len:
self.l('pass')
if is_empty:
self.l('pass')
5 changes: 3 additions & 2 deletions fastjsonschema/draft06.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,14 @@ def generate_property_names(self):
self.l('{variable}_property_names = True')
with self.l('for {variable}_key in {variable}:'):
with self.l('try:'):
count = self.generate_func_code_block(
code_len = len(self._code)
self.generate_func_code_block(
property_names_definition,
'{}_key'.format(self._variable),
self._variable_name,
clear_variables=True,
)
if not count:
if len(self._code) == code_len:
self.l('pass')
with self.l('except JsonSchemaValueException:'):
self.l('{variable}_property_names = False')
Expand Down
6 changes: 6 additions & 0 deletions fastjsonschema/draft07.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,28 @@ def generate_if_then_else(self):
self.l('pass')
with self.l('except JsonSchemaValueException:'):
if 'else' in self._definition:
code_len = len(self._code)
self.generate_func_code_block(
self._definition['else'],
self._variable,
self._variable_name,
clear_variables=True
)
if len(self._code) == code_len:
self.l('pass')
else:
self.l('pass')
if 'then' in self._definition:
with self.l('else:'):
code_len = len(self._code)
self.generate_func_code_block(
self._definition['then'],
self._variable,
self._variable_name,
clear_variables=True
)
if len(self._code) == code_len:
self.l('pass')

def generate_content_encoding(self):
"""
Expand Down
11 changes: 11 additions & 0 deletions tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,14 @@ def test_issue_114(asserter):
value = {"a": []}
expected = value
asserter(schema, value, expected)


def test_items_subschema_without_validation_code(asserter):
asserter({'items': {'not': False}}, [1, 2], [1, 2])


def test_additional_items_subschema_without_validation_code(asserter):
asserter({
'items': [{'type': 'string'}],
'additionalItems': {'format': 'unknown-format'},
}, ['a', 1], ['a', 1])
32 changes: 32 additions & 0 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,35 @@ def test_one_of_factorized(asserter, value, expected):
])
def test_not(asserter, value, expected):
asserter({'not': {'type': 'number'}}, value, expected)


exc = JsonSchemaValueException('data must NOT match a disallowed definition', value='{data}', name='data', definition='{definition}', rule='not')
@pytest.mark.parametrize('subdefinition', [
{'title': 'x'},
{'description': 'd'},
{'$comment': 'c'},
{'examples': [1]},
{'default': 5},
{'readOnly': True},
{'not': False},
{'format': 'unknown-format'},
{'uniqueItems': False},
])
@pytest.mark.parametrize('value', [0, 'abc', {}])
def test_not_subschema_without_validation_code(asserter, subdefinition, value):
"""Subschema generating no validation code matches everything, so not must reject everything."""
asserter({'not': subdefinition}, value, exc)


@pytest.mark.parametrize('schema_version', [
'http://json-schema.org/draft-04/schema',
'http://json-schema.org/draft-06/schema',
'http://json-schema.org/draft-07/schema',
'https://json-schema.org/draft/2019-09/schema',
])
def test_not_annotation_only_all_drafts(asserter, schema_version):
asserter({'$schema': schema_version, 'not': {'title': 'x'}}, 1, exc)


def test_not_not_annotation_only(asserter):
asserter({'not': {'not': {'title': 'x'}}}, 1, 1)
28 changes: 28 additions & 0 deletions tests/test_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,31 @@ def test_if_then_empty_if():
"version": "2.0",
"terminal": "t1",
}


def test_if_then_else_annotation_only_then():
schema = {
"$schema": "http://json-schema.org/draft-07/schema",
"if": {"type": "string"},
"then": {"title": "x"},
"else": {"type": "integer"},
}
validator = fastjsonschema.compile(schema)
assert validator("abc") == "abc"
assert validator(42) == 42
with pytest.raises(JsonSchemaValueException):
validator(4.5)


def test_if_then_else_annotation_only_else():
schema = {
"$schema": "http://json-schema.org/draft-07/schema",
"if": {"type": "string"},
"then": {"minLength": 2},
"else": {"title": "x"},
}
validator = fastjsonschema.compile(schema)
assert validator("abc") == "abc"
assert validator(42) == 42
with pytest.raises(JsonSchemaValueException):
validator("a")
16 changes: 16 additions & 0 deletions tests/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,19 @@ def test_property_names_description_only():
compile(code, '<string>', 'exec')
validator = fastjsonschema.compile(schema)
assert validator({'build': {}, 'test': 1}) == {'build': {}, 'test': 1}


def test_property_names_subschema_without_validation_code():
schema = {
'type': 'object',
'propertyNames': {'not': False},
}
validator = fastjsonschema.compile(schema)
assert validator({'build': {}, 'test': 1}) == {'build': {}, 'test': 1}


def test_dependencies_annotation_only_schema():
schema = {'dependencies': {'foo': {'title': 'x'}}}
validator = fastjsonschema.compile(schema)
assert validator({'foo': 1}) == {'foo': 1}
assert validator({'bar': 1}) == {'bar': 1}