summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKen'ichi Ohmichi <oomichi@mxs.nes.nec.co.jp>2013-10-28 21:45:27 +0900
committerKen'ichi Ohmichi <oomichi@mxs.nes.nec.co.jp>2013-11-02 04:12:30 +0900
commitf191f32a722ef0c2eaad71dd33da4e7787ac2424 (patch)
tree3e1e29e479156db5d57ec794780f3482b26c49c3
parenta59576226dd4affde0afdd028f54c423b8786e24 (diff)
downloadwsme-f191f32a722ef0c2eaad71dd33da4e7787ac2424.tar.gz
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
-rw-r--r--setup.py3
-rw-r--r--wsme/tests/test_types.py59
-rw-r--r--wsme/types.py140
3 files changed, 202 insertions, 0 deletions
diff --git a/setup.py b/setup.py
index 2553ca9..1fbb3bf 100644
--- a/setup.py
+++ b/setup.py
@@ -17,6 +17,9 @@ install_requires = [
if sys.version_info[:2] <= (2, 6):
install_requires += ('ordereddict',)
+if sys.version_info[:2] < (3, 3):
+ install_requires += ('ipaddr',)
+
setup(
setup_requires=['pbr>=0.5.21'],
install_requires=install_requires,
diff --git a/wsme/tests/test_types.py b/wsme/tests/test_types.py
index e40bd7d..1c28495 100644
--- a/wsme/tests/test_types.py
+++ b/wsme/tests/test_types.py
@@ -1,3 +1,4 @@
+import re
try:
import unittest2 as unittest
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.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):
self.assertRaises(ValueError, types.register_type, [])
self.assertRaises(ValueError, types.register_type, [int, str])
diff --git a/wsme/types.py b/wsme/types.py
index 991450b..0902ce3 100644
--- a/wsme/types.py
+++ b/wsme/types.py
@@ -3,10 +3,17 @@ import datetime
import decimal
import inspect
import logging
+import re
import six
import sys
+import uuid
import weakref
+try:
+ import ipaddress
+except ImportError:
+ import ipaddr as ipaddress
+
from wsme import exc
log = logging.getLogger(__name__)
@@ -136,6 +143,139 @@ class BinaryType(UserType):
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):
"""
A simple enumeration type. Can be based on any non-complex type.