Add validation for incomming test results

Test results are validated using jsonschema.
Actual schema can be retrieved from /v1/results/schema endpoint.

https://github.com/stackforge/refstack/blob/master/specs/proposed/refstack-org-test-result-json-schema.rst

Change-Id: I05e5fa3b84c60925f162985cd7d51b14d69b19d8
This commit is contained in:
sslypushenko 2015-02-06 18:57:36 +02:00
parent 4e390ee67b
commit 7e8af456c4
5 changed files with 179 additions and 30 deletions

View File

@ -20,47 +20,74 @@ import pecan
from pecan import rest
from refstack import db
from refstack.common import validators
logger = logging.getLogger(__name__)
class ResultsController(rest.RestController):
class RestControllerWithValidation(rest.RestController):
"""
Controller provides validation for POSTed data
exposed endpoints:
POST base_url/
GET base_url/<item uid>
GET base_url/schema
"""
def __init__(self, validator):
self.validator = validator
def get_item(self, item_id):
"""Handler for getting item"""
raise NotImplemented
def store_item(self, item_in_json):
"""Handler for storing item. Should return new item id"""
raise NotImplemented
@pecan.expose('json')
def get_one(self, arg):
"""Return test results in JSON format.
:param arg: item ID in uuid4 format or action
"""
if self.validator.assert_id(arg):
return self.get_item(item_id=arg)
elif arg == 'schema':
return self.validator.schema
else:
pecan.abort(404)
@pecan.expose('json')
def post(self, ):
"""POST handler."""
item = validators.safe_load_json_body(self.validator)
item_id = self.store_item(item)
pecan.response.status = 201
return item_id
class ResultsController(RestControllerWithValidation):
"""/v1/results handler."""
@pecan.expose('json')
def get(self, ):
"""GET handler."""
return {'Result': 'Ok'}
@pecan.expose("json")
def get_one(self, test_id):
"""Return test results in JSON format.
:param test_id: ID of the test to get the JSON for.
"""
test_info = db.get_test(test_id)
def get_item(self, item_id):
"""Handler for getting item"""
test_info = db.get_test(item_id)
if not test_info:
pecan.abort(404)
test_list = db.get_test_results(test_id)
test_list = db.get_test_results(item_id)
test_name_list = [test_dict[0] for test_dict in test_list]
return {"cpid": test_info.cpid,
"created_at": test_info.created_at,
"duration_seconds": test_info.duration_seconds,
"results": test_name_list}
@pecan.expose(template='json')
def post(self, ):
"""POST handler."""
try:
results = pecan.request.json
except ValueError:
return pecan.abort(400,
detail='Request body \'%s\' could not '
'be decoded as JSON.'
'' % pecan.request.body)
test_id = db.store_results(results)
def store_item(self, item_in_json):
"""Handler for storing item. Should return new item id"""
test_id = db.store_results(item_in_json)
return {'test_id': test_id}
@ -68,4 +95,4 @@ class V1Controller(object):
"""Version 1 API controller root."""
results = ResultsController()
results = ResultsController(validators.TestResultValidator())

View File

View File

@ -0,0 +1,121 @@
#
# All Rights Reserved.
#
# 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.
""" Validators module
"""
import uuid
import json
import jsonschema
import pecan
from pecan import request
ext_format_checker = jsonschema.FormatChecker()
def is_uuid(inst):
""" Check that inst is a uuid_hex string. """
try:
uuid.UUID(hex=inst)
except (TypeError, ValueError):
return False
return True
@jsonschema.FormatChecker.checks(ext_format_checker,
format='uuid_hex',
raises=(TypeError, ValueError))
def checker_uuid(inst):
"""Checker 'uuid_hex' format for jsonschema validator"""
return is_uuid(inst)
class Validator(object):
"""Base class for validators"""
def validate(self, json_data):
"""
:param json_data: data for validation
"""
jsonschema.validate(json_data, self.schema)
class TestResultValidator(Validator):
"""Validator for incoming test results."""
def __init__(self):
self.schema = {
'type': 'object',
'properties': {
'cpid': {
'type': 'string'
},
'duration_seconds': {'type': 'integer'},
'results': {
"type": "array",
"items": [{
'type': 'object',
'properties': {
'name': {'type': 'string'},
'uid': {
'type': 'string',
'format': 'uuid_hex'
}
}
}]
}
},
'required': ['cpid', 'duration_seconds', 'results'],
'additionalProperties': False
}
jsonschema.Draft4Validator.check_schema(self.schema)
self.validator = jsonschema.Draft4Validator(
self.schema,
format_checker=ext_format_checker
)
@staticmethod
def assert_id(_id):
""" Check that _id is a valid uuid_hex string. """
return is_uuid(_id)
def safe_load_json_body(validator):
"""
Helper for load validated request body
:param validator: instance of Validator class
:return validated body
:raise ValueError, jsonschema.ValidationError
"""
body = ''
try:
body = json.loads(request.body)
except (ValueError, TypeError) as e:
pecan.abort(400, detail=e.message)
try:
validator.validate(body)
except jsonschema.ValidationError as e:
pecan.abort(400,
detail=e.message,
title='Malformed json data, '
'see %s/schema' % request.path_url)
return body

View File

@ -8,3 +8,4 @@ pecan>=0.8.2
pyOpenSSL==0.13
pycrypto==2.6
requests==1.2.3
jsonschema>=2.0.0,<3.0.0

View File

@ -76,12 +76,12 @@ https://github.com/stackforge/refstack/blob/master/specs/approved/api-v1.md
**failed response:** http:400 - Malformed data.
{
'message': 'malformed json data, see /v1/schema/results.json'
'message': 'Malformed json data, see /v1/results/schema'
}
**url:** get /v1/schema/results.json
**url:** get /v1/results/schema
**valid response:** http:200 results.json file
**valid response:** http:200 schema.json file
No invalid responses. No accepted parameters.