forked from GithubBackups/healthchecks
Move json validation code to a separate file, add support for array and enum, add tests.
This commit is contained in:
parent
1c5182278e
commit
c5568b6dd1
@ -4,7 +4,7 @@ from functools import wraps
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpResponseBadRequest, JsonResponse
|
||||
from six import string_types
|
||||
from hc.lib.jsonschema import ValidationError, validate
|
||||
|
||||
|
||||
def uuid_or_400(f):
|
||||
@ -61,21 +61,10 @@ def validate_json(schema):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(request, *args, **kwds):
|
||||
for key, spec in schema["properties"].items():
|
||||
if key not in request.json:
|
||||
continue
|
||||
|
||||
value = request.json[key]
|
||||
if spec["type"] == "string":
|
||||
if not isinstance(value, string_types):
|
||||
return make_error("%s is not a string" % key)
|
||||
elif spec["type"] == "number":
|
||||
if not isinstance(value, int):
|
||||
return make_error("%s is not a number" % key)
|
||||
if "minimum" in spec and value < spec["minimum"]:
|
||||
return make_error("%s is too small" % key)
|
||||
if "maximum" in spec and value > spec["maximum"]:
|
||||
return make_error("%s is too large" % key)
|
||||
try:
|
||||
validate(request.json, schema)
|
||||
except ValidationError as e:
|
||||
return make_error("json validation error: %s" % e)
|
||||
|
||||
return f(request, *args, **kwds)
|
||||
return wrapper
|
||||
|
44
hc/lib/jsonschema.py
Normal file
44
hc/lib/jsonschema.py
Normal file
@ -0,0 +1,44 @@
|
||||
""" A minimal jsonschema validator.
|
||||
|
||||
Supports only a tiny subset of jsonschema.
|
||||
|
||||
"""
|
||||
|
||||
from six import string_types
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def validate(obj, schema, obj_name="value"):
|
||||
if schema.get("type") == "string":
|
||||
if not isinstance(obj, string_types):
|
||||
raise ValidationError("%s is not a string" % obj_name)
|
||||
|
||||
elif schema.get("type") == "number":
|
||||
if not isinstance(obj, int):
|
||||
raise ValidationError("%s is not a number" % obj_name)
|
||||
if "minimum" in schema and obj < schema["minimum"]:
|
||||
raise ValidationError("%s is too small" % obj_name)
|
||||
if "maximum" in schema and obj > schema["maximum"]:
|
||||
raise ValidationError("%s is too large" % obj_name)
|
||||
|
||||
elif schema.get("type") == "array":
|
||||
if not isinstance(obj, list):
|
||||
raise ValidationError("%s is not an array" % obj_name)
|
||||
|
||||
for v in obj:
|
||||
validate(v, schema["items"], "an item in '%s'" % obj_name)
|
||||
|
||||
elif schema.get("type") == "object":
|
||||
if not isinstance(obj, dict):
|
||||
raise ValidationError("%s is not an object" % obj_name)
|
||||
|
||||
for key, spec in schema["properties"].items():
|
||||
if key in obj:
|
||||
validate(obj[key], spec, obj_name=key)
|
||||
|
||||
if "enum" in schema:
|
||||
if obj not in schema["enum"]:
|
||||
raise ValidationError("%s has unexpected value" % obj_name)
|
76
hc/lib/tests/test_jsonschema.py
Normal file
76
hc/lib/tests/test_jsonschema.py
Normal file
@ -0,0 +1,76 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from hc.lib.jsonschema import ValidationError, validate
|
||||
|
||||
|
||||
class JsonSchemaTestCase(TestCase):
|
||||
|
||||
def test_it_validates_strings(self):
|
||||
validate("foo", {"type": "string"})
|
||||
|
||||
def test_it_checks_string_type(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate(123, {"type": "string"})
|
||||
|
||||
def test_it_validates_numbers(self):
|
||||
validate(123, {"type": "number", "minimum": 0, "maximum": 1000})
|
||||
|
||||
def test_it_checks_int_type(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate("foo", {"type": "number"})
|
||||
|
||||
def test_it_checks_min_value(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate(5, {"type": "number", "minimum": 10})
|
||||
|
||||
def test_it_checks_max_value(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate(5, {"type": "number", "maximum": 0})
|
||||
|
||||
def test_it_validates_objects(self):
|
||||
validate({"foo": "bar"}, {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {"type": "string"}
|
||||
}
|
||||
})
|
||||
|
||||
def test_it_checks_dict_type(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate("not-object", {"type": "object"})
|
||||
|
||||
def test_it_validates_objects_properties(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate({"foo": "bar"}, {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"foo": {"type": "number"}
|
||||
}
|
||||
})
|
||||
|
||||
def test_it_validates_arrays(self):
|
||||
validate(["foo", "bar"], {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
})
|
||||
|
||||
def test_it_validates_array_type(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate("not-an-array", {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
})
|
||||
|
||||
def test_it_validates_array_elements(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate(["foo", "bar"], {
|
||||
"type": "array",
|
||||
"items": {"type": "number"}
|
||||
})
|
||||
|
||||
def test_it_validates_enum(self):
|
||||
validate("foo", {"enum": ["foo", "bar"]})
|
||||
|
||||
def test_it_rejects_a_value_not_in_enum(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
validate("baz", {"enum": ["foo", "bar"]})
|
Loading…
x
Reference in New Issue
Block a user