Base validation framework implemented

* validate(schema, *validators) - method validates request body using the
  specified jsonschema and then validate request body using validators
  (callables);

* check_exists(get_func, *id_prop, **get_args) - method checks that object
  is available using specifed function and magic props for defining id-arg
  mapping for calling function;

* ApiValidator added w/ support of uuid type;

* unit tests for ApiValidator has been added.

Partially implements blueprint savanna-rest-api-1-0-validation

Change-Id: Ieb2b9c46ab58e7ec4900016f21164d07bcfba30d
This commit is contained in:
Sergey Lukjanov 2013-06-25 15:11:56 +04:00
parent 79fcbd1319
commit 3ea5c7776d
3 changed files with 282 additions and 0 deletions

View File

@ -0,0 +1,83 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 functools
import jsonschema
from savanna import exceptions as ex
import savanna.openstack.common.exception as os_ex
from savanna.utils import api as u
from savanna.utils import api_validator
def validate(schema, *validators):
def decorator(func):
@functools.wraps(func)
def handler(*args, **kwargs):
request_data = u.request_data()
try:
if schema:
validator = api_validator.ApiValidator(schema)
validator.validate(request_data)
if validators:
for validator in validators:
validator(request_data)
except jsonschema.ValidationError, e:
e.code = "VALIDATION_ERROR"
return u.bad_request(e)
except ex.SavannaException, e:
return u.bad_request(e)
except os_ex.MalformedRequestBody, e:
e.code = "MALFORMED_REQUEST_BODY"
return u.bad_request(e)
except Exception, e:
return u.internal_error(
500, "Error occurred during validation", e)
return func(*args, **kwargs)
return handler
return decorator
def check_exists(get_func, *id_prop, **get_args):
def decorator(func):
@functools.wraps(func)
def handler(*args, **kwargs):
if id_prop and not get_args:
get_args['id'] = id_prop[0]
get_kwargs = {}
for get_arg in get_args:
get_kwargs[get_arg] = kwargs[get_args[get_arg]]
obj = None
try:
obj = get_func(**get_kwargs)
except Exception, e:
if 'notfound' not in e.__class__.__name__.lower():
raise e
if obj is None:
e = ex.NotFoundException(get_kwargs,
'Object with %s not found')
return u.not_found(e)
return func(*args, **kwargs)
return handler
return decorator

View File

@ -0,0 +1,169 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 jsonschema
import unittest2
from savanna.openstack.common import uuidutils
from savanna.utils import api_validator
def _validate(schema, data):
validator = api_validator.ApiValidator(schema)
validator.validate(data)
class ApiValidatorTest(unittest2.TestCase):
def _validate_success(self, schema, data):
return _validate(schema, data)
def _validate_failure(self, schema, data):
self.assertRaises(jsonschema.ValidationError, _validate, schema, data)
def test_validate_required(self):
schema = {
"type": "object",
"properties": {
"prop-1": {
"type": "string",
},
},
}
self._validate_success(schema, {
"prop-1": "asd",
})
self._validate_success(schema, {
"prop-2": "asd",
})
schema["required"] = ["prop-1"]
self._validate_success(schema, {
"prop-1": "asd",
})
self._validate_failure(schema, {
"prop-2": "asd",
})
def test_validate_additionalProperties(self):
schema = {
"type": "object",
"properties": {
"prop-1": {
"type": "string",
},
},
"required": ["prop-1"]
}
self._validate_success(schema, {
"prop-1": "asd",
})
self._validate_success(schema, {
"prop-1": "asd",
"prop-2": "asd",
})
schema["additionalProperties"] = True
self._validate_success(schema, {
"prop-1": "asd",
})
self._validate_success(schema, {
"prop-1": "asd",
"prop-2": "asd",
})
schema["additionalProperties"] = False
self._validate_success(schema, {
"prop-1": "asd",
})
self._validate_failure(schema, {
"prop-1": "asd",
"prop-2": "asd",
})
def test_validate_string(self):
schema = {
"type": "string",
}
self._validate_success(schema, "asd")
self._validate_success(schema, "")
self._validate_failure(schema, 1)
self._validate_failure(schema, 1.5)
self._validate_failure(schema, True)
def test_validate_string_with_length(self):
schema = {
"type": "string",
"minLength": 1,
"maxLength": 10,
}
self._validate_success(schema, "a")
self._validate_success(schema, "a" * 10)
self._validate_failure(schema, "")
self._validate_failure(schema, "a" * 11)
def test_validate_integer(self):
schema = {
'type': 'integer',
}
self._validate_success(schema, 0)
self._validate_success(schema, 1)
self._validate_failure(schema, "1")
self._validate_failure(schema, "a")
self._validate_failure(schema, True)
def test_validate_integer_w_range(self):
schema = {
'type': 'integer',
'minimum': 1,
'maximum': 10,
}
self._validate_success(schema, 1)
self._validate_success(schema, 10)
self._validate_failure(schema, 0)
self._validate_failure(schema, 11)
def test_validate_uuid(self):
schema = {
"type": "string",
"format": "uuid",
}
uuid = uuidutils.generate_uuid()
self._validate_success(schema, uuid)
self._validate_failure(schema, uuid.replace("-", ""))
def test_validate_hostname(self):
schema = {
"type": "string",
"format": "hostname",
}
self._validate_success(schema, "abcd")
self._validate_success(schema, "abcd123")
self._validate_success(schema, "abcd-123")
self._validate_failure(schema, "abcd_123")
self._validate_failure(schema, "_123")
self._validate_failure(schema, "a" * 64)
self._validate_failure(schema, "")

View File

@ -0,0 +1,30 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 jsonschema
from savanna.openstack.common import uuidutils
@jsonschema.FormatChecker.cls_checks('uuid')
def validate_uuid_format(entry):
return uuidutils.is_uuid_like(entry)
class ApiValidator(jsonschema.Draft4Validator):
def __init__(self, schema):
format_checker = jsonschema.FormatChecker()
super(ApiValidator, self).__init__(schema,
format_checker=format_checker)