Skip to content

Commit

Permalink
Merge pull request #78 from d-ganchar/feature/74
Browse files Browse the repository at this point in the history
#74 list as json root
  • Loading branch information
d-ganchar authored Jun 15, 2021
2 parents 28b5761 + 328c377 commit a14bea2
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 9 deletions.
13 changes: 13 additions & 0 deletions flask_request_validator/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ def __init__(self, depth: List[str], errors: Dict[int, RequestError], as_list: b
self.as_list = as_list


class JsonListExpectedError(JsonError):
def __init__(self, depth: List[str]):
self.depth = depth

def __str__(self) -> str:
return 'list type expected'


class JsonDictExpectedError(JsonListExpectedError):
def __str__(self) -> str:
return 'dict type expected'


class JsonListItemTypeError(RequestError):
"""
Raises when invalid type of list item.
Expand Down
29 changes: 22 additions & 7 deletions flask_request_validator/nested_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
JsonError,
RequiredJsonKeyError,
JsonListItemTypeError,
RulesError,
RulesError, JsonListExpectedError, JsonDictExpectedError,
)
from .rules import CompositeRule, AbstractRule

Expand Down Expand Up @@ -62,9 +62,11 @@ def _validate_list(
continue

if isinstance(node, dict):
value, errors, rules_err = self._validate_dict(node, nested, depth, errors)
item_value, errors, rules_err = self._validate_dict(node, nested, depth, errors)
if rules_err:
n_err[ix] = rules_err
else:
value[ix] = item_value
continue

try:
Expand Down Expand Up @@ -121,6 +123,12 @@ def _check_required(self, key: str, value: dict, rule: Any):
if isinstance(rule, JsonParam) and rule.required and key not in value:
raise RequiredJsonKeyError(key)

def _check_as_list_value(self, nested: 'JsonParam', value: Any, depth: list):
if nested.as_list and not isinstance(value, list):
raise JsonListExpectedError(depth)
if not nested.as_list and not isinstance(value, dict):
raise JsonListExpectedError(depth)

def validate(
self,
value: Union[Dict, List],
Expand All @@ -132,17 +140,24 @@ def validate(
errors = errors or []
node_errors = dict()
nested = nested or self
if isinstance(nested.rules_map, dict) and not nested.as_list:

try:
self._check_as_list_value(nested, value, depth)
except (JsonListExpectedError, JsonDictExpectedError) as e:
errors.append(e)
return value, errors

if nested.as_list:
value, errors = self._validate_list(value, nested, depth, errors)
return value, errors

if isinstance(nested.rules_map, dict):
for key, rule in nested.rules_map.items():
try:
self._check_required(key, value, rule)
except RequiredJsonKeyError as e:
node_errors[key] = e

if nested.as_list:
value, errors = self._validate_list(value, nested, depth, errors)
return value, errors

value, errors, nested_errors = self._validate_dict(value, nested, depth, errors)
node_errors.update(nested_errors)
errors = self._collect_errors(depth, errors, node_errors)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

setup(
name='flask_request_validator',
version='4.1.0',
version='4.2.0',
description='Flask request data validation',
long_description=long_description,
url='https://github.com/d-ganchar/flask_request_validator',
Expand Down
70 changes: 69 additions & 1 deletion tests/test_nested_json.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import unittest
from copy import deepcopy

from parameterized import parameterized

from flask_request_validator.exceptions import *
from flask_request_validator import JsonParam, Enum, CompositeRule, Min, Max, IsEmail
from flask_request_validator import JsonParam, Enum, CompositeRule, Min, Max, IsEmail, Number, MinLength


class TestJsonParam(unittest.TestCase):
Expand Down Expand Up @@ -202,3 +204,69 @@ def test_list(self, param: JsonParam, data, exp):
else:
# RulesError
self.assertTrue(isinstance(json_er.errors[rules_ix], rules_err))

@parameterized.expand([
(
[
{'age': 10, 'name': 'test'},
{'age': 20, 'name': 'test2'},
{'age': 30, 'name': 'test3'},
],
),
(
[
{'age': 10, 'name': 'test', 'tags': [{'name': 'green'}, {'name': 'light'}]},
{'age': 20, 'name': 'test2'},
{'age': 30, 'name': 'test3', 'tags': [{'name': 'cat'}, {'name': 'dog'}]},
],
),
])
def test_root_list_valid(self, value):
param = JsonParam({
'age': [Number()],
'name': [MinLength(1), ],
'tags': JsonParam({'name': [MinLength(1)]}, required=False, as_list=True)
}, as_list=True)

valid_value, errors = param.validate(deepcopy(value))
self.assertListEqual(value, valid_value)
self.assertEqual(0, len(errors))

def test_root_list_invalid(self):
param = JsonParam({
'age': [Number()],
'name': [MinLength(1), ],
'tags': JsonParam({'name': [MinLength(1)]}, required=False, as_list=True)
}, as_list=True)
# invalid values
_, errors = param.validate([
{'age': 'ab', 'name': 'test'},
{'age': 'c', 'name': ''},
{'age': 15, 'name': 'good'},
])

self.assertEqual(1, len(errors))
self.assertTrue(isinstance(errors[0], JsonError))
error = errors[0]
self.assertListEqual(['root'], error.depth)
self.assertTrue(2, len(error.errors))
self.assertTrue(error.errors[0], 1)
self.assertTrue(error.errors[1], 2)

self.assertTrue(isinstance(error.errors[0]['age'].errors[0], NumberError))
self.assertTrue(isinstance(error.errors[1]['age'].errors[0], NumberError))
self.assertTrue(isinstance(error.errors[1]['name'].errors[0], ValueMinLengthError))
# invalid type - dict instead list
_, errors = param.validate({'age': 18, 'name': 'test'})
self.assertEqual(1, len(errors))
self.assertListEqual(['root'], errors[0].depth)
self.assertTrue(isinstance(errors[0], JsonListExpectedError))
# invalid type - nested string instead list
_, errors = param.validate([
{'age': 27, 'name': 'test'},
{'age': 15, 'name': 'good', 'tags': 'bad_type'},
])

self.assertEqual(1, len(errors))
self.assertListEqual(['root', 'tags'], errors[0].depth)
self.assertTrue(isinstance(errors[0], JsonListExpectedError))

0 comments on commit a14bea2

Please sign in to comment.