Browse Source

Move json validation code to a separate file, add support for array and enum, add tests.

pull/90/head
Pēteris Caune 8 years ago
parent
commit
c5568b6dd1
3 changed files with 125 additions and 16 deletions
  1. +5
    -16
      hc/api/decorators.py
  2. +44
    -0
      hc/lib/jsonschema.py
  3. +76
    -0
      hc/lib/tests/test_jsonschema.py

+ 5
- 16
hc/api/decorators.py View File

@ -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
- 0
hc/lib/jsonschema.py View 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
- 0
hc/lib/tests/test_jsonschema.py View 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…
Cancel
Save