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:
parent
79fcbd1319
commit
3ea5c7776d
83
savanna/service/validation.py
Normal file
83
savanna/service/validation.py
Normal 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
|
169
savanna/tests/unit/test_api_validator.py
Normal file
169
savanna/tests/unit/test_api_validator.py
Normal 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, "")
|
30
savanna/utils/api_validator.py
Normal file
30
savanna/utils/api_validator.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user