Add IntegerType and some classes for validation
This patch adds the following classes for API parameter validation: IntegerType * Value range validation (minimum, maximum) StringType * String length validation (min_length, max_length) * Allowed string (pattern): e.g. should contain [a-zA-Z0-9_.- ] only. IPv4AddressType * String format validation for IPv4 IPv6AddressType * String format validation for IPv6 UuidType * String format validation for UUID Partially implements blueprint nova-api-validation-fw Closes-Bug: 1245795 Change-Id: I5aead6c51b74464681e4ac41fa2a9c66c09adab2
This commit is contained in:
parent
a59576226d
commit
f191f32a72
3
setup.py
3
setup.py
@ -17,6 +17,9 @@ install_requires = [
|
|||||||
if sys.version_info[:2] <= (2, 6):
|
if sys.version_info[:2] <= (2, 6):
|
||||||
install_requires += ('ordereddict',)
|
install_requires += ('ordereddict',)
|
||||||
|
|
||||||
|
if sys.version_info[:2] < (3, 3):
|
||||||
|
install_requires += ('ipaddr',)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
setup_requires=['pbr>=0.5.21'],
|
setup_requires=['pbr>=0.5.21'],
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
try:
|
try:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -293,6 +294,64 @@ Value: 'v3'. Value should be one of: v., v.",
|
|||||||
self.assertEqual(types.validate_value(int, six.u('1')), 1)
|
self.assertEqual(types.validate_value(int, six.u('1')), 1)
|
||||||
self.assertRaises(ValueError, types.validate_value, int, 1.1)
|
self.assertRaises(ValueError, types.validate_value, int, 1.1)
|
||||||
|
|
||||||
|
def test_validate_integer_type(self):
|
||||||
|
v = types.IntegerType(minimum=1, maximum=10)
|
||||||
|
v.validate(1)
|
||||||
|
v.validate(5)
|
||||||
|
v.validate(10)
|
||||||
|
self.assertRaises(ValueError, v.validate, 0)
|
||||||
|
self.assertRaises(ValueError, v.validate, 11)
|
||||||
|
|
||||||
|
def test_validate_string_type(self):
|
||||||
|
v = types.StringType(min_length=1, max_length=10,
|
||||||
|
pattern='^[a-zA-Z0-9]*$')
|
||||||
|
v.validate('1')
|
||||||
|
v.validate('12345')
|
||||||
|
v.validate('1234567890')
|
||||||
|
self.assertRaises(ValueError, v.validate, '')
|
||||||
|
self.assertRaises(ValueError, v.validate, '12345678901')
|
||||||
|
|
||||||
|
# Test a pattern validation
|
||||||
|
v.validate('a')
|
||||||
|
v.validate('A')
|
||||||
|
self.assertRaises(ValueError, v.validate, '_')
|
||||||
|
|
||||||
|
def test_validate_string_type_precompile(self):
|
||||||
|
precompile = re.compile('^[a-zA-Z0-9]*$')
|
||||||
|
v = types.StringType(min_length=1, max_length=10,
|
||||||
|
pattern=precompile)
|
||||||
|
|
||||||
|
# Test a pattern validation
|
||||||
|
v.validate('a')
|
||||||
|
v.validate('A')
|
||||||
|
self.assertRaises(ValueError, v.validate, '_')
|
||||||
|
|
||||||
|
def test_validate_ipv4_address_type(self):
|
||||||
|
v = types.IPv4AddressType()
|
||||||
|
v.validate('127.0.0.1')
|
||||||
|
v.validate('192.168.0.1')
|
||||||
|
self.assertRaises(ValueError, v.validate, '')
|
||||||
|
self.assertRaises(ValueError, v.validate, 'foo')
|
||||||
|
self.assertRaises(ValueError, v.validate,
|
||||||
|
'2001:0db8:bd05:01d2:288a:1fc0:0001:10ee')
|
||||||
|
|
||||||
|
def test_validate_ipv6_address_type(self):
|
||||||
|
v = types.IPv6AddressType()
|
||||||
|
v.validate('0:0:0:0:0:0:0:1')
|
||||||
|
v.validate('2001:0db8:bd05:01d2:288a:1fc0:0001:10ee')
|
||||||
|
self.assertRaises(ValueError, v.validate, '')
|
||||||
|
self.assertRaises(ValueError, v.validate, 'foo')
|
||||||
|
self.assertRaises(ValueError, v.validate, '192.168.0.1')
|
||||||
|
|
||||||
|
def test_validate_uuid_type(self):
|
||||||
|
v = types.UuidType()
|
||||||
|
v.validate('6a0a707c-45ef-4758-b533-e55adddba8ce')
|
||||||
|
v.validate('6a0a707c45ef4758b533e55adddba8ce')
|
||||||
|
self.assertRaises(ValueError, v.validate, '')
|
||||||
|
self.assertRaises(ValueError, v.validate, 'foo')
|
||||||
|
self.assertRaises(ValueError, v.validate,
|
||||||
|
'6a0a707c-45ef-4758-b533-e55adddba8ce-a')
|
||||||
|
|
||||||
def test_register_invalid_array(self):
|
def test_register_invalid_array(self):
|
||||||
self.assertRaises(ValueError, types.register_type, [])
|
self.assertRaises(ValueError, types.register_type, [])
|
||||||
self.assertRaises(ValueError, types.register_type, [int, str])
|
self.assertRaises(ValueError, types.register_type, [int, str])
|
||||||
|
140
wsme/types.py
140
wsme/types.py
@ -3,10 +3,17 @@ import datetime
|
|||||||
import decimal
|
import decimal
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import six
|
import six
|
||||||
import sys
|
import sys
|
||||||
|
import uuid
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ipaddress
|
||||||
|
except ImportError:
|
||||||
|
import ipaddr as ipaddress
|
||||||
|
|
||||||
from wsme import exc
|
from wsme import exc
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -136,6 +143,139 @@ class BinaryType(UserType):
|
|||||||
binary = BinaryType()
|
binary = BinaryType()
|
||||||
|
|
||||||
|
|
||||||
|
class IntegerType(UserType):
|
||||||
|
"""
|
||||||
|
A simple integer type. Can validate a value range.
|
||||||
|
|
||||||
|
:param minimum: Possible minimum value
|
||||||
|
:param maximum: Possible maximum value
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
Price = IntegerType(minimum=1)
|
||||||
|
|
||||||
|
"""
|
||||||
|
basetype = int
|
||||||
|
name = "integer"
|
||||||
|
|
||||||
|
def __init__(self, minimum=None, maximum=None):
|
||||||
|
self.minimum = minimum
|
||||||
|
self.maximum = maximum
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def frombasetype(value):
|
||||||
|
return int(value) if value is not None else None
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
if self.minimum is not None and value < self.minimum:
|
||||||
|
error = 'Value should be greater or equal to %s' % self.minimum
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
if self.maximum is not None and value > self.maximum:
|
||||||
|
error = 'Value should be lower or equal to %s' % self.maximum
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class StringType(UserType):
|
||||||
|
"""
|
||||||
|
A simple string type. Can validate a length and a pattern.
|
||||||
|
|
||||||
|
:param min_length: Possible minimum length
|
||||||
|
:param max_length: Possible maximum length
|
||||||
|
:param pattern: Possible string pattern
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
Name = StringType(min_length=1, pattern='^[a-zA-Z ]*$')
|
||||||
|
|
||||||
|
"""
|
||||||
|
basetype = six.string_types
|
||||||
|
name = "string"
|
||||||
|
|
||||||
|
def __init__(self, min_length=None, max_length=None, pattern=None):
|
||||||
|
self.min_length = min_length
|
||||||
|
self.max_length = max_length
|
||||||
|
if isinstance(pattern, six.string_types):
|
||||||
|
self.pattern = re.compile(pattern)
|
||||||
|
else:
|
||||||
|
self.pattern = pattern
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
if not isinstance(value, self.basetype):
|
||||||
|
error = 'Value should be string'
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
if self.min_length is not None and len(value) < self.min_length:
|
||||||
|
error = 'Value should have a minimum character requirement of %s' \
|
||||||
|
% self.min_length
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
if self.max_length is not None and len(value) > self.max_length:
|
||||||
|
error = 'Value should have a maximum character requirement of %s' \
|
||||||
|
% self.max_length
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
if self.pattern is not None and not self.pattern.search(value):
|
||||||
|
error = 'Value should match the pattern %s' % self.pattern
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class IPv4AddressType(UserType):
|
||||||
|
"""
|
||||||
|
A simple IPv4 type.
|
||||||
|
"""
|
||||||
|
basetype = six.string_types
|
||||||
|
name = "ipv4address"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value):
|
||||||
|
try:
|
||||||
|
ipaddress.IPv4Address(value)
|
||||||
|
except ipaddress.AddressValueError:
|
||||||
|
error = 'Value should be IPv4 format'
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
|
||||||
|
class IPv6AddressType(UserType):
|
||||||
|
"""
|
||||||
|
A simple IPv6 type.
|
||||||
|
"""
|
||||||
|
basetype = six.string_types
|
||||||
|
name = "ipv6address"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value):
|
||||||
|
try:
|
||||||
|
ipaddress.IPv6Address(value)
|
||||||
|
except ipaddress.AddressValueError:
|
||||||
|
error = 'Value should be IPv6 format'
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
|
||||||
|
class UuidType(UserType):
|
||||||
|
"""
|
||||||
|
A simple UUID type.
|
||||||
|
|
||||||
|
This type allows not only UUID having dashes but also UUID not
|
||||||
|
having dashes. For example, '6a0a707c-45ef-4758-b533-e55adddba8ce'
|
||||||
|
and '6a0a707c45ef4758b533e55adddba8ce' are distinguished as valid.
|
||||||
|
"""
|
||||||
|
basetype = six.string_types
|
||||||
|
name = "uuid"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value):
|
||||||
|
try:
|
||||||
|
uuid.UUID(value)
|
||||||
|
except (TypeError, ValueError, AttributeError):
|
||||||
|
error = 'Value should be UUID format'
|
||||||
|
raise ValueError(error)
|
||||||
|
|
||||||
|
|
||||||
class Enum(UserType):
|
class Enum(UserType):
|
||||||
"""
|
"""
|
||||||
A simple enumeration type. Can be based on any non-complex type.
|
A simple enumeration type. Can be based on any non-complex type.
|
||||||
|
Loading…
Reference in New Issue
Block a user