1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 11:18:11 +00:00

Use schema to set appliance default values and better schema validation error messages.

This commit is contained in:
grossmj 2018-11-16 23:02:10 +07:00
parent 1acc7777f9
commit 627c7e9cfe
3 changed files with 608 additions and 168 deletions

View File

@ -58,7 +58,8 @@ class ApplianceHandler:
400: "Invalid request" 400: "Invalid request"
}, },
input=APPLIANCE_CREATE_SCHEMA, input=APPLIANCE_CREATE_SCHEMA,
output=APPLIANCE_OBJECT_SCHEMA) output=APPLIANCE_OBJECT_SCHEMA,
set_input_schema_defaults=True)
def create(request, response): def create(request, response):
controller = Controller.instance() controller = Controller.instance()

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ import aiohttp
import logging import logging
import traceback import traceback
import jsonschema import jsonschema
import jsonschema.exceptions
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -36,7 +36,30 @@ from ..crash_report import CrashReport
from ..config import Config from ..config import Config
async def parse_request(request, input_schema, raw): # Add default values for missing entries in a request, largely taken from jsonschema documentation example
# https://python-jsonschema.readthedocs.io/en/latest/faq/#why-doesn-t-my-schema-s-default-property-set-the-default-on-my-instance
def extend_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"]
def set_defaults(validator, properties, instance, schema):
if jsonschema.Draft4Validator(schema).is_valid(instance):
# only add default for the matching sub-schema (e.g. when using 'oneOf')
for property, subschema in properties.items():
if "default" in subschema:
instance.setdefault(property, subschema["default"])
for error in validate_properties(validator, properties, instance, schema,):
yield error
return jsonschema.validators.extend(
validator_class, {"properties" : set_defaults},
)
ValidatorWithDefaults = extend_with_default(jsonschema.Draft4Validator)
async def parse_request(request, input_schema, raw, set_input_schema_defaults=False):
"""Parse body of request and raise HTTP errors in case of problems""" """Parse body of request and raise HTTP errors in case of problems"""
request.json = {} request.json = {}
@ -55,16 +78,35 @@ async def parse_request(request, input_schema, raw):
request.json[k] = v[0] request.json[k] = v[0]
if input_schema: if input_schema:
if set_input_schema_defaults:
validator = ValidatorWithDefaults(input_schema)
else:
validator = jsonschema.Draft4Validator(input_schema)
try: try:
jsonschema.validate(request.json, input_schema) validator.validate(request.json)
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
log.error("Invalid input query. JSON schema error: {}".format(e.message)) best_match = jsonschema.exceptions.best_match(validator.iter_errors(request.json))
raise aiohttp.web.HTTPBadRequest(text="Invalid JSON: {} in schema: {}".format( message = "JSON schema error with API request '{}': {} (best matched error: {})".format(request.path_qs, e.message, best_match.message)
e.message, log.error(message)
json.dumps(e.schema))) log.debug("Input schema: {}".format(json.dumps(input_schema)))
raise aiohttp.web.HTTPBadRequest(text=message)
return request return request
# if set_input_schema_defaults:
# validator = ValidatorWithDefaults(input_schema)
# else:
# validator = jsonschema.Draft4Validator(input_schema)
# error = jsonschema.exceptions.best_match(validator.iter_errors(request.json))
# if error:
# message = "JSON schema error with API request '{}' while validating JSON data '{}': {}".format(request.path_qs, request.json, error.message)
# log.error(message)
# log.debug("Input schema: {}".format(json.dumps(input_schema)))
# raise aiohttp.web.HTTPBadRequest(text=message)
#
# return request
class Route(object): class Route(object):
@ -132,6 +174,7 @@ class Route(object):
input_schema = kw.get("input", {}) input_schema = kw.get("input", {})
api_version = kw.get("api_version", 2) api_version = kw.get("api_version", 2)
raw = kw.get("raw", False) raw = kw.get("raw", False)
set_input_schema_defaults = kw.get("set_input_schema_defaults", False)
def register(func): def register(func):
# Add the type of server to the route # Add the type of server to the route
@ -180,7 +223,7 @@ class Route(object):
return response return response
# API call # API call
request = await parse_request(request, input_schema, raw) request = await parse_request(request, input_schema, raw, set_input_schema_defaults)
record_file = server_config.get("record") record_file = server_config.get("record")
if record_file: if record_file:
try: try: