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:
Ken'ichi Ohmichi 2013-10-28 21:45:27 +09:00
parent a59576226d
commit f191f32a72
3 changed files with 202 additions and 0 deletions

View File

@ -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,

View File

@ -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])

View File

@ -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.