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.contrib.auth.models import User
|
||||||
from django.http import HttpResponseBadRequest, JsonResponse
|
from django.http import HttpResponseBadRequest, JsonResponse
|
||||||
from six import string_types
|
from hc.lib.jsonschema import ValidationError, validate
|
||||||
|
|
||||||
|
|
||||||
def uuid_or_400(f):
|
def uuid_or_400(f):
|
||||||
@ -61,21 +61,10 @@ def validate_json(schema):
|
|||||||
def decorator(f):
|
def decorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(request, *args, **kwds):
|
def wrapper(request, *args, **kwds):
|
||||||
for key, spec in schema["properties"].items():
|
try:
|
||||||
if key not in request.json:
|
validate(request.json, schema)
|
||||||
continue
|
except ValidationError as e:
|
||||||
|
return make_error("json validation error: %s" % e)
|
||||||
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)
|
|
||||||
|
|
||||||
return f(request, *args, **kwds)
|
return f(request, *args, **kwds)
|
||||||
return wrapper
|
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