Enhance the validation of tosca

Enhance the validation of tosca with a parent constraint class
and different constraint sub-classes.

Implements: blueprint tosca-validation
Change-Id: I5c31c13c3d9cc36c8af6355472d243652bdc1eff
This commit is contained in:
Yaoguo Jiang
2014-06-25 07:51:53 +00:00
parent ae70ab835b
commit b5e01c0826
4 changed files with 698 additions and 61 deletions

View File

@@ -13,68 +13,80 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import datetime
import numbers
import re
from translator.toscalib.utils.gettextutils import _
class ValidationError(Exception):
def __init__(self, message):
self.message = message
def __str__(self):
return str(self.message)
class Constraint(object):
'''Parent class for constraints for a Property or Input.'''
CONSTRAINTS = (EQUAL, GREATER_THAN,
GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL, IN_RANGE,
VALID_VALUES, LENGTH, MIN_LENGHT, MAX_LENGTH, PATTERN) = \
VALID_VALUES, LENGTH, MIN_LENGTH, MAX_LENGTH, PATTERN) = \
('equal', 'greater_than', 'greater_or_equal', 'less_than',
'less_or_equal', 'in_range', 'valid_values', 'length',
'min_length', 'max_length', 'pattern')
def __init__(self, propertyname, value, constraint):
self.propertyname = propertyname
self.value = value
self.constraint = constraint
PROPERTY_TYPES = (
INTEGER, STRING, BOOLEAN,
FLOAT, TIMESTAMP
) = (
'integer', 'string', 'boolean',
'float', 'timestamp'
)
def validate(self):
for key, value in self.constraint.items():
if key == self.GREATER_OR_EQUAL:
self.validate_greater_than(value)
def __new__(cls, property_name, property_type, constraint):
if cls is not Constraint:
return super(Constraint, cls).__new__(cls)
def validate_equal(self):
pass
if(not isinstance(constraint, collections.Mapping) or
len(constraint) != 1):
raise ValidationError(_('Invalid constraint schema.'))
def validate_greater_than(self, value):
if self.value < value:
raise ValueError(_("%(prop)s value requires to be "
"greater than %(val)s")
% {'prop': self.propertyname, 'val': value})
for type in constraint.keys():
ConstraintClass = get_constraint_class(type)
if not ConstraintClass:
raise ValidationError(_('Invalid constraint type "%s".') %
type)
def validate_greater_or_equal(self):
pass
return ConstraintClass(property_name, property_type, constraint)
def validate_less_than(self):
pass
def __init__(self, property_name, property_type, constraint):
self.property_name = property_name
self.property_type = property_type
self.constraint_value = constraint[self.constraint_key]
# check if constraint is valid for property type
if property_type not in self.valid_prop_types:
raise ValidationError(_('Constraint type "%(ctype)s" is not valid '
'for data type "%(dtype)s".') %
{'ctype': self.constraint_key,
'dtype': property_type})
def validate_less_or_equal(self):
pass
def _err_msg(self, value):
return _('Property %s could not be validated.') % self.property_name
def validate_in_range(self):
pass
def validate_valid_values(self):
pass
def validate_length(self):
pass
def validate_min_length(self):
pass
def validate_max_length(self):
pass
def validate_pattern(self):
pass
def validate(self, value):
if not self._is_valid(value):
err_msg = self._err_msg(value)
raise ValidationError(err_msg)
@staticmethod
def validate_integer(value):
if not isinstance(value, (int, long)):
raise TypeError(_('Value is not an integer for %s') % value)
if not isinstance(value, int):
raise ValueError(_('Value is not an integer for %s.') % value)
return Constraint.validate_number(value)
@staticmethod
@@ -83,13 +95,15 @@ class Constraint(object):
@staticmethod
def validate_string(value):
if not isinstance(value, basestring):
raise ValueError(_('Value must be a string %s') % value)
if not isinstance(value, str):
raise ValueError(_('Value must be a string %s.') % value)
return value
@staticmethod
def validate_list(self, value):
pass
if not isinstance(value, collections.Sequence):
raise ValueError(_('Value must be a list %s.') % value)
return value
@staticmethod
def str_to_num(value):
@@ -100,3 +114,385 @@ class Constraint(object):
return int(value)
except ValueError:
return float(value)
class Equal(Constraint):
"""Constraint class for "equal"
Constrains a property or parameter to a value equal to ('=')
the value declared.
"""
constraint_key = Constraint.EQUAL
valid_prop_types = Constraint.PROPERTY_TYPES
def _is_valid(self, value):
if value == self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s is not equal to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class GreaterThan(Constraint):
"""Constraint class for "greater_than"
Constrains a property or parameter to a value greater than ('>')
the value declared.
"""
constraint_key = Constraint.GREATER_THAN
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Constraint.INTEGER, Constraint.FLOAT,
Constraint.TIMESTAMP)
def __init__(self, property_name, property_type, constraint):
super(GreaterThan, self).__init__(property_name, property_type,
constraint)
if not isinstance(constraint[self.GREATER_THAN], self.valid_types):
raise ValidationError(_('greater_than must be comparable.'))
def _is_valid(self, value):
if value > self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be greater than "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class GreaterOrEqual(Constraint):
"""Constraint class for "greater_or_equal"
Constrains a property or parameter to a value greater than or equal
to ('>=') the value declared.
"""
constraint_key = Constraint.GREATER_OR_EQUAL
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Constraint.INTEGER, Constraint.FLOAT,
Constraint.TIMESTAMP)
def __init__(self, property_name, property_type, constraint):
super(GreaterOrEqual, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise ValidationError(_('greater_or_equal must be comparable.'))
def _is_valid(self, value):
if value >= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be greater or equal '
'to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class LessThan(Constraint):
"""Constraint class for "less_than"
Constrains a property or parameter to a value less than ('<')
the value declared.
"""
constraint_key = Constraint.LESS_THAN
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Constraint.INTEGER, Constraint.FLOAT,
Constraint.TIMESTAMP)
def __init__(self, property_name, property_type, constraint):
super(LessThan, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise ValidationError(_('less_than must be comparable.'))
def _is_valid(self, value):
if value < self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be less than "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class LessOrEqual(Constraint):
"""Constraint class for "less_or_equal"
Constrains a property or parameter to a value less than or equal
to ('<=') the value declared.
"""
constraint_key = Constraint.LESS_OR_EQUAL
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Constraint.INTEGER, Constraint.FLOAT,
Constraint.TIMESTAMP)
def __init__(self, property_name, property_type, constraint):
super(LessOrEqual, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise ValidationError(_('less_or_equal must be comparable.'))
def _is_valid(self, value):
if value <= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s must be less or '
'equal to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class InRange(Constraint):
"""Constraint class for "in_range"
Constrains a property or parameter to a value in range of (inclusive)
the two values declared.
"""
constraint_key = Constraint.IN_RANGE
valid_types = (int, float, datetime.date,
datetime.time, datetime.datetime)
valid_prop_types = (Constraint.INTEGER, Constraint.FLOAT,
Constraint.TIMESTAMP)
def __init__(self, property_name, property_type, constraint):
super(InRange, self).__init__(property_name, property_type, constraint)
if(not isinstance(self.constraint_value, collections.Sequence) or
(len(constraint[self.IN_RANGE]) != 2)):
raise ValidationError(_('in_range must be a list.'))
for value in self.constraint_value:
if not isinstance(value, self.valid_types):
raise ValidationError(_('in_range value must be comparable.'))
self.min = self.constraint_value[0]
self.max = self.constraint_value[1]
def _is_valid(self, value):
if value < self.min:
return False
if value > self.max:
return False
return True
def _err_msg(self, value):
return (_('%(pname)s: %(pvalue)s is out of range '
'(min:%(vmin)s, max:%(vmax)s).') %
dict(pname=self.property_name,
pvalue=value,
vmin=self.min,
vmax=self.max))
class ValidValues(Constraint):
"""Constraint class for "valid_values"
Constrains a property or parameter to a value that is in the list of
declared values.
"""
constraint_key = Constraint.VALID_VALUES
valid_prop_types = Constraint.PROPERTY_TYPES
def __init__(self, property_name, property_type, constraint):
super(ValidValues, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, collections.Sequence):
raise ValidationError(_('valid_values must be a list.'))
def _is_valid(self, value):
if isinstance(value, collections.Sequence):
return all(v in self.constraint_value for v in value)
return value in self.constraint_value
def _err_msg(self, value):
allowed = '[%s]' % ', '.join(str(a) for a in self.constraint_value)
return (_('%(pname)s: %(pvalue)s is not an valid '
'value "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=allowed))
class Length(Constraint):
"""Constraint class for "length"
Constrains the property or parameter to a value of a given length.
"""
constraint_key = Constraint.LENGTH
valid_types = (int, )
valid_prop_types = (Constraint.STRING, )
def __init__(self, property_name, property_type, constraint):
super(Length, self).__init__(property_name, property_type, constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise ValidationError(_('length must be integer.'))
def _is_valid(self, value):
if isinstance(value, str) and len(value) == self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('length of %(pname)s: %(pvalue)s must be equal '
'to "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class MinLength(Constraint):
"""Constraint class for "min_length"
Constrains the property or parameter to a value to a minimum length.
"""
constraint_key = Constraint.MIN_LENGTH
valid_types = (int, )
valid_prop_types = (Constraint.STRING, )
def __init__(self, property_name, property_type, constraint):
super(MinLength, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise ValidationError(_('min_length must be integer.'))
def _is_valid(self, value):
if isinstance(value, str) and len(value) >= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('length of %(pname)s: %(pvalue)s must be '
'at least "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class MaxLength(Constraint):
"""Constraint class for "max_length"
Constrains the property or parameter to a value to a maximum length.
"""
constraint_key = Constraint.MAX_LENGTH
valid_types = (int, )
valid_prop_types = (Constraint.STRING, )
def __init__(self, property_name, property_type, constraint):
super(MaxLength, self).__init__(property_name, property_type,
constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise ValidationError(_('max_length must be integer.'))
def _is_valid(self, value):
if isinstance(value, str) and len(value) <= self.constraint_value:
return True
return False
def _err_msg(self, value):
return (_('length of %(pname)s: %(pvalue)s must be no greater '
'than "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
class Pattern(Constraint):
"""Constraint class for "pattern"
Constrains the property or parameter to a value that is allowed by
the provided regular expression.
"""
constraint_key = Constraint.PATTERN
valid_types = (str, )
valid_prop_types = (Constraint.STRING, )
def __init__(self, property_name, property_type, constraint):
super(Pattern, self).__init__(property_name, property_type, constraint)
if not isinstance(self.constraint_value, self.valid_types):
raise ValidationError(_('pattern must be string.'))
self.match = re.compile(self.constraint_value).match
def _is_valid(self, value):
match = self.match(value)
return match is not None and match.end() == len(value)
def _err_msg(self, value):
return (_('%(pname)s: "%(pvalue)s" does not match '
'pattern "%(cvalue)s".') %
dict(pname=self.property_name,
pvalue=value,
cvalue=self.constraint_value))
constraint_mapping = {
Constraint.EQUAL: Equal,
Constraint.GREATER_THAN: GreaterThan,
Constraint.GREATER_OR_EQUAL: GreaterOrEqual,
Constraint.LESS_THAN: LessThan,
Constraint.LESS_OR_EQUAL: LessOrEqual,
Constraint.IN_RANGE: InRange,
Constraint.VALID_VALUES: ValidValues,
Constraint.LENGTH: Length,
Constraint.MIN_LENGTH: MinLength,
Constraint.MAX_LENGTH: MaxLength,
Constraint.PATTERN: Pattern
}
def get_constraint_class(type):
return constraint_mapping.get(type)

View File

@@ -74,14 +74,12 @@ class Input(object):
field=name)
def validate_type(self, input_type):
if input_type not in Property.PROPERTIY_TYPES:
if input_type not in Constraint.PROPERTY_TYPES:
raise ValueError(_('Invalid type %s') % type)
def validate_constraints(self, constraints):
for constraint in constraints:
for key in constraint.keys():
if key not in Constraint.CONSTRAINTS:
raise ValueError(_('Invalid constraint %s') % constraint)
Constraint(self.name, self.type, constraint)
class Output(object):

View File

@@ -24,13 +24,6 @@ class Property(object):
) = (
'type', 'required', 'description', 'default', 'constraints'
)
PROPERTIY_TYPES = (
INTEGER, STRING, BOOLEAN,
FLOAT, TIMESTAMP
) = (
'integer', 'string', 'boolean',
'float', 'timestamp'
)
def __init__(self, property_name, value, schema=None):
self.name = property_name
@@ -59,26 +52,28 @@ class Property(object):
def validate(self):
'''Validate if not a reference property.'''
if not isinstance(self.value, dict):
self._validate_constraints()
self._validate_datatype()
self._validate_constraints()
def _validate_datatype(self):
try:
if self.TYPE in self.schema:
dtype = self.schema[self.TYPE]
if dtype == self.STRING:
if dtype == Constraint.STRING:
return Constraint.validate_string(self.value)
elif dtype == self.INTEGER:
elif dtype == Constraint.INTEGER:
return Constraint.validate_integer(self.value)
elif dtype == self.NUMBER:
elif dtype == Constraint.NUMBER:
return Constraint.validate_number(self.value)
elif dtype == self.LIST:
elif dtype == Constraint.LIST:
return Constraint.validate_list(self.value)
except Exception:
pass
def _validate_constraints(self):
constraints = self.constraints
dtype = self.schema[self.TYPE]
if constraints:
for constraint in constraints:
Constraint(self.name, self.value, constraint).validate()
Constraint(self.name, dtype,
constraint).validate(self.value)

View File

@@ -0,0 +1,248 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import yaml
from translator.toscalib.elements.constraints import Constraint
from translator.toscalib.elements.constraints import ValidationError
from translator.toscalib.tests.base import TestCase
class ConstraintTest(TestCase):
def test_invalid_constraint_type(self):
schema = {'invalid_type': 2}
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.INTEGER,
schema)
self.assertEqual('Invalid constraint type "invalid_type".',
str(error))
def test_invalid_prop_type(self):
schema = {'length': 5}
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.INTEGER,
schema)
self.assertEqual('Constraint type "length" is not valid for '
'data type "integer".', str(error))
def test_invalid_validvalues(self):
schema = {'valid_values': 2}
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.INTEGER,
schema)
self.assertEqual('valid_values must be a list.', str(error))
def test_validvalues_validate(self):
schema = {'valid_values': [2, 4, 6, 8]}
constraint = Constraint('prop', Constraint.INTEGER, schema)
self.assertIsNone(constraint.validate(4))
def test_validvalues_validate_fail(self):
schema = {'valid_values': [2, 4, 6, 8]}
constraint = Constraint('prop', Constraint.INTEGER, schema)
error = self.assertRaises(ValidationError, constraint.validate, 5)
self.assertEqual('prop: 5 is not an valid value "[2, 4, 6, 8]".',
str(error))
def test_invalid_in_range(self):
snippet = 'in_range: {2, 6}'
schema = yaml.load(snippet)
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.INTEGER,
schema)
self.assertEqual('in_range must be a list.', str(error))
def test_in_range_min_max(self):
schema = {'in_range': [2, 6]}
constraint = Constraint('prop', Constraint.INTEGER, schema)
self.assertEqual(2, constraint.min)
self.assertEqual(6, constraint.max)
def test_in_range_validate(self):
schema = {'in_range': [2, 6]}
constraint = Constraint('prop', Constraint.INTEGER, schema)
self.assertIsNone(constraint.validate(2))
self.assertIsNone(constraint.validate(4))
self.assertIsNone(constraint.validate(6))
def test_in_range_validate_fail(self):
schema = {'in_range': [2, 6]}
constraint = Constraint('prop', Constraint.INTEGER, schema)
error = self.assertRaises(ValidationError, constraint.validate, 8)
self.assertEqual('prop: 8 is out of range (min:2, max:6).',
str(error))
def test_equal_validate(self):
schema = {'equal': 4}
constraint = Constraint('prop', Constraint.INTEGER, schema)
self.assertIsNone(constraint.validate(4))
def test_equal_validate_fail(self):
schema = {'equal': 4}
constraint = Constraint('prop', Constraint.INTEGER, schema)
error = self.assertRaises(ValidationError, constraint.validate, 8)
self.assertEqual('prop: 8 is not equal to "4".', str(error))
def test_greater_than_validate(self):
schema = {'greater_than': 4}
constraint = Constraint('prop', Constraint.INTEGER, schema)
self.assertIsNone(constraint.validate(6))
def test_greater_than_validate_fail(self):
schema = {'greater_than': 4}
constraint = Constraint('prop', Constraint.INTEGER, schema)
error = self.assertRaises(ValidationError, constraint.validate, 3)
self.assertEqual('prop: 3 must be greater than "4".', str(error))
error = self.assertRaises(ValidationError, constraint.validate, 4)
self.assertEqual('prop: 4 must be greater than "4".', str(error))
def test_greater_or_equal_validate(self):
schema = {'greater_or_equal': 3.9}
constraint = Constraint('prop', Constraint.FLOAT, schema)
self.assertIsNone(constraint.validate(3.9))
self.assertIsNone(constraint.validate(4.0))
def test_greater_or_equal_validate_fail(self):
schema = {'greater_or_equal': 3.9}
constraint = Constraint('prop', Constraint.FLOAT, schema)
error = self.assertRaises(ValidationError, constraint.validate, 3.0)
self.assertEqual('prop: 3.0 must be greater or equal to "3.9".',
str(error))
error = self.assertRaises(ValidationError, constraint.validate, 3.8)
self.assertEqual('prop: 3.8 must be greater or equal to "3.9".',
str(error))
def test_less_than_validate(self):
schema = {'less_than': datetime.date(2014, 0o7, 25)}
constraint = Constraint('prop', Constraint.TIMESTAMP, schema)
self.assertIsNone(constraint.validate(datetime.date(2014, 0o7, 20)))
self.assertIsNone(constraint.validate(datetime.date(2014, 0o7, 24)))
def test_less_than_validate_fail(self):
schema = {'less_than': datetime.date(2014, 0o7, 25)}
constraint = Constraint('prop', Constraint.TIMESTAMP, schema)
error = self.assertRaises(ValidationError,
constraint.validate,
datetime.date(2014, 0o7, 25))
self.assertEqual('prop: 2014-07-25 must be '
'less than "2014-07-25".',
str(error))
error = self.assertRaises(ValidationError,
constraint.validate,
datetime.date(2014, 0o7, 27))
self.assertEqual('prop: 2014-07-27 must be '
'less than "2014-07-25".',
str(error))
def test_less_or_equal_validate(self):
schema = {'less_or_equal': 4}
constraint = Constraint('prop', Constraint.INTEGER, schema)
self.assertIsNone(constraint.validate(4))
self.assertIsNone(constraint.validate(3))
def test_less_or_equal_validate_fail(self):
schema = {'less_or_equal': 4}
constraint = Constraint('prop', Constraint.INTEGER, schema)
error = self.assertRaises(ValidationError, constraint.validate, 5)
self.assertEqual('prop: 5 must be less or equal to "4".', str(error))
def test_invalid_length(self):
schema = {'length': 'four'}
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.STRING,
schema)
self.assertEqual('length must be integer.', str(error))
schema = {'length': 4.5}
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.STRING,
schema)
self.assertEqual('length must be integer.', str(error))
def test_length_validate(self):
schema = {'length': 4}
constraint = Constraint('prop', Constraint.STRING, schema)
self.assertIsNone(constraint.validate('abcd'))
def test_length_validate_fail(self):
schema = {'length': 4}
constraint = Constraint('prop', Constraint.STRING, schema)
error = self.assertRaises(ValidationError, constraint.validate, 'abc')
self.assertEqual('length of prop: abc must be equal to "4".',
str(error))
error = self.assertRaises(ValidationError,
constraint.validate,
'abcde')
self.assertEqual('length of prop: abcde must be equal to "4".',
str(error))
def test_invalid_min_length(self):
schema = {'min_length': 'four'}
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.STRING,
schema)
self.assertEqual('min_length must be integer.', str(error))
def test_min_length_validate(self):
schema = {'min_length': 4}
constraint = Constraint('prop', Constraint.STRING, schema)
self.assertIsNone(constraint.validate('abcd'))
self.assertIsNone(constraint.validate('abcde'))
def test_min_length_validate_fail(self):
schema = {'min_length': 4}
constraint = Constraint('prop', Constraint.STRING, schema)
error = self.assertRaises(ValidationError, constraint.validate, 'abc')
self.assertEqual('length of prop: abc must be at least "4".',
str(error))
def test_invalid_max_length(self):
schema = {'max_length': 'four'}
error = self.assertRaises(ValidationError, Constraint,
'prop', Constraint.STRING,
schema)
self.assertEqual('max_length must be integer.', str(error))
def test_max_length_validate(self):
schema = {'max_length': 4}
constraint = Constraint('prop', Constraint.STRING, schema)
self.assertIsNone(constraint.validate('abcd'))
self.assertIsNone(constraint.validate('abc'))
def test_max_length_validate_fail(self):
schema = {'max_length': 4}
constraint = Constraint('prop', Constraint.STRING, schema)
error = self.assertRaises(ValidationError,
constraint.validate,
'abcde')
self.assertEqual('length of prop: abcde must be no greater than "4".',
str(error))
def test_pattern_validate(self):
schema = {'pattern': '[0-9]*'}
constraint = Constraint('prop', Constraint.STRING, schema)
self.assertIsNone(constraint.validate('123'))
def test_pattern_validate_fail(self):
schema = {'pattern': '[0-9]*'}
constraint = Constraint('prop', Constraint.STRING, schema)
error = self.assertRaises(ValidationError, constraint.validate, 'abc')
self.assertEqual('prop: "abc" does not match pattern "[0-9]*".',
str(error))