diff --git a/pykwalify/core.py b/pykwalify/core.py index 07ec47f..3b314a7 100644 --- a/pykwalify/core.py +++ b/pykwalify/core.py @@ -50,8 +50,6 @@ def __init__(self, source_file=None, schema_files=[], source_data=None, schema_d elif source_file.endswith(".yaml"): try: self.source = yaml.load(stream) - # if len(self.source) == 0: - # raise Exception(".") except Exception as e: raise CoreError("Unable to load any data from source yaml file") else: @@ -201,8 +199,6 @@ def _validate_sequence(self, value, rule, path, errors=[], done=None): Log.debug(" * Seq: {}".format(rule._sequence)) Log.debug(" * Map: {}".format(rule._mapping)) - if not isinstance(rule._sequence, list): - raise CoreError("sequence data not of list type : {}".format(path)) if not len(rule._sequence) == 1: raise CoreError("only 1 item allowed in sequence rule : {}".format(path)) @@ -280,17 +276,6 @@ def _validate_mapping(self, value, rule, path, errors=[], done=None): Log.debug(" + No rule to apply, prolly because of allowempty: True") return - if not isinstance(rule._mapping, dict): - raise CoreError("mapping is not a valid dict object") - - if value is None: - Log.debug(" + Value is None, returning...") - return - - if not isinstance(value, dict): - errors.append("mapping.value.notdict : {} : {}".format(value, path)) - return - m = rule._mapping Log.debug(" + RuleMapping: {}".format(m)) @@ -345,11 +330,6 @@ def _validate_scalar(self, value, rule, path, errors=[], done=None): Log.debug(" # {}".format(rule._type)) Log.debug(" # {}".format(path)) - if rule._sequence is not None: - raise CoreError("found sequence when validating for scalar") - if rule._mapping is not None: - raise CoreError("found mapping when validating for scalar") - if rule._enum is not None: if value not in rule._enum: errors.append("enum.notexists : {} : {}".format(value, path)) @@ -390,35 +370,25 @@ def _validate_scalar(self, value, rule, path, errors=[], done=None): "scalar") def _validate_range(self, max_, min_, max_ex, min_ex, errors, value, path, prefix): - ########## - # Test max + """ + Validate that value is within range values. + """ + Log.debug("Validate range : {} : {} : {} : {} : {} : {}".format(max_, min_, max_ex, min_ex, value, path)) if max_ is not None: - if not isinstance(max_, int): - raise Exception("INTERNAL ERROR: variable 'max' not of 'int' type") - if max_ < value: errors.append("{}.range.toolarge : {} < {} : {}".format(prefix, max_, value, path)) if min_ is not None: - if not isinstance(min_, int): - raise Exception("INTERNAL ERROR: variable 'min_' not of 'int' type") - if min_ > value: errors.append("{}.range.toosmall : {} > {} : {}".format(prefix, min_, value, path)) if max_ex is not None: - if not isinstance(max_ex, int): - raise Exception("INTERNAL ERROR: variable 'max_ex' not of 'int' type") - if max_ex <= value: errors.append("{}.range.tolarge-ex : {} <= {} : {}".format(prefix, max_ex, value, path)) if min_ex is not None: - if not isinstance(min_ex, int): - raise Exception("INTERNAL ERROR: variable 'min_ex' not of 'int' type") - if min_ex >= value: errors.append("{}.range.toosmall-ex : {} >= {} : {}".format(prefix, min_ex, value, path)) diff --git a/pykwalify/rule.py b/pykwalify/rule.py index 9058525..b910166 100644 --- a/pykwalify/rule.py +++ b/pykwalify/rule.py @@ -13,7 +13,7 @@ Log = logging.getLogger(__name__) # pyKwalify imports -from pykwalify.types import DEFAULT_TYPE, typeClass, isBuiltinType, isCollectionType, isInt +from pykwalify.types import DEFAULT_TYPE, typeClass, isBuiltinType, isCollectionType, isInt, isBool from pykwalify.errors import SchemaConflict, RuleError @@ -230,16 +230,16 @@ def initRangeValue(self, v, rule, path): max_ex = self._range.get("max-ex", None) min_ex = self._range.get("min-ex", None) - if max is not None and not isInt(max): + if max is not None and not isInt(max) or isBool(max): raise RuleError("range.max.notint : {} : {}".format(max, path)) - if min is not None and not isInt(min): + if min is not None and not isInt(min) or isBool(min): raise RuleError("range.min.notint : {} : {}".format(min, path)) - if max_ex is not None and not isInt(max_ex): + if max_ex is not None and not isInt(max_ex) or isBool(max_ex): raise RuleError("range.max_ex.notint : {} : {}".format(max_ex, path)) - if min_ex is not None and not isInt(min_ex): + if min_ex is not None and not isInt(min_ex) or isBool(min_ex): raise RuleError("range.min_ex.notint : {} : {}".format(min_ex, path)) if max is not None: diff --git a/tests/files/14b.yaml b/tests/files/14b.yaml index a391ee2..fbd95c4 100644 --- a/tests/files/14b.yaml +++ b/tests/files/14b.yaml @@ -17,8 +17,8 @@ sequence: age: type: int range: - max: 30 - min: 18 + max-ex: 30 + min-ex: 18 blood: type: str enum: diff --git a/tests/files/15a.yaml b/tests/files/15a.yaml index 40937ee..4da313a 100644 --- a/tests/files/15a.yaml +++ b/tests/files/15a.yaml @@ -9,4 +9,9 @@ email: bar@mail.net age: 15 blood: AB - birth: "1980/01/01" \ No newline at end of file + birth: "1980/01/01" +- email: bar@mail.net + age: 20 + blood: AB + birth: "1980/01/01" + name: bar diff --git a/tests/files/15b.yaml b/tests/files/15b.yaml new file mode 100644 index 0000000..796a709 --- /dev/null +++ b/tests/files/15b.yaml @@ -0,0 +1,34 @@ +type: seq +sequence: + - type: map + mapping: + name: + type: str + required: True + email: + type: str + required: True + pattern: .+@.+ + password: + type: str + range: + max: 16 + min: 8 + age: + type: int + range: + max-ex: 19 + min-ex: 18 + blood: + type: str + enum: + - A + - B + - O + - AB + birth: + type: str # Should be date + memo: + type: any + deleted: + type: bool diff --git a/tests/test_cli.py b/tests/test_cli.py index 1ed8cfd..05c5f01 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -61,4 +61,4 @@ def test_run_cli(self): cli_args = cli.parse_cli() c = cli.run(cli_args) - assert c.validation_errors == [] \ No newline at end of file + assert c.validation_errors == [] diff --git a/tests/test_types.py b/tests/test_types.py index dee04b1..edb62e6 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -55,7 +55,7 @@ def test_types(self): assert types.isFloat(1.0) assert not types.isFloat("foo") - + assert types.isNumber(1) assert types.isNumber(1.0) assert not types.isNumber("foo") diff --git a/tests/testcore.py b/tests/testcore.py index 4c8cc9f..0bf29ea 100644 --- a/tests/testcore.py +++ b/tests/testcore.py @@ -177,6 +177,15 @@ def test_validation_error_but_not_raise_exception(self): # TODO: Fix this issue... # assert ('pykwalify.core', 'ERROR', 'Errors found but will not raise exception...') in l.actual() + def test_invalid_mapping_data_type(self): + """ + Test that validating range object raises correct error messages when wrong + values is sent into the function. + """ + # c = Core(source_data={"foo": None}, schema_data={"type": "map", "mapping": {"foo": {"type": "str"}}}) + # c.validate() + pass + def testCoreDataMode(self): Core(source_data=3.14159, schema_data={"type": "number"}).validate() Core(source_data=3.14159, schema_data={"type": "float"}).validate() @@ -263,7 +272,7 @@ def testCore(self): # Test mapping with sequence with mapping and valid data ("12a.yaml", "12b.yaml", {'mapping': {'company': {'required': True, 'type': 'str'}, 'email': {'type': 'str'}, 'employees': {'sequence': [{'mapping': {'code': {'required': True, 'type': 'int'}, 'email': {'type': 'str'}, 'name': {'required': True, 'type': 'str'}}, 'type': 'map'}], 'type': 'seq'}}, 'type': 'map'}), # Test most of the implemented functions - ("14a.yaml", "14b.yaml", {'sequence': [{'mapping': {'age': {'range': {'max': 30, 'min': 18}, 'type': 'int'}, 'birth': {'type': 'str'}, 'blood': {'enum': ['A', 'B', 'O', 'AB'], 'type': 'str'}, 'deleted': {'type': 'bool'}, 'email': {'pattern': '.+@.+', 'required': True, 'type': 'str'}, 'memo': {'type': 'any'}, 'name': {'required': True, 'type': 'str'}, 'password': {'range': {'max': 16, 'min': 8}, 'type': 'str'}}, 'type': 'map'}], 'type': 'seq'}), + ("14a.yaml", "14b.yaml", {'sequence': [{'mapping': {'age': {'range': {'max-ex': 30, 'min-ex': 18}, 'type': 'int'}, 'birth': {'type': 'str'}, 'blood': {'enum': ['A', 'B', 'O', 'AB'], 'type': 'str'}, 'deleted': {'type': 'bool'}, 'email': {'pattern': '.+@.+', 'required': True, 'type': 'str'}, 'memo': {'type': 'any'}, 'name': {'required': True, 'type': 'str'}, 'password': {'range': {'max': 16, 'min': 8}, 'type': 'str'}}, 'type': 'map'}], 'type': 'seq'}), # This will test the unique constraint ("16a.yaml", "16b.yaml", {'sequence': [{'mapping': {'email': {'type': 'str'}, 'groups': {'sequence': [{'type': 'str', 'unique': True}], 'type': 'seq'}, 'name': {'required': True, 'type': 'str', 'unique': True}}, 'required': True, 'type': 'map'}], 'type': 'seq'}), # @@ -316,15 +325,17 @@ def testCore(self): ("13a.yaml", "12b.yaml", SchemaError, ["Value: A101 is not of type 'int' : /employees/0/code", 'key.undefined : mail : /employees/1']), # TODO: write - ("15a.yaml", "14b.yaml", SchemaError, ["Value: twenty is not of type 'int' : /0/age", + ("15a.yaml", "15b.yaml", SchemaError, ["Value: twenty is not of type 'int' : /0/age", 'pattern.unmatch : .+@.+ --> foo(at)mail.com : /0/email', 'enum.notexists : a : /0/blood', 'required.nokey : name : /1', 'key.undefined : given-name : /1', 'key.undefined : family-name : /1', + 'scalar.range.toosmall-ex : 18 >= 15 : /1/age', + 'scalar.range.toosmall-ex : 18 >= 6 : /0/age', 'scalar.range.toosmall : 8 > 6 : /0/password', - 'scalar.range.toosmall : 18 > 15 : /1/age', - 'scalar.range.toosmall : 18 > 6 : /0/age']), + 'scalar.range.tolarge-ex : 19 <= 20 : /2/age', + ]), # TODO: The reverse unique do not currently work proper # This will test the unique constraint but should fail ("17a.yaml", "16b.yaml", SchemaError, ['value.notunique :: value: foo : /0/groups/3 : /0/groups/0']), # This tests number validation rule with wrong data