2016-10-25 14:53:31 +01:00
|
|
|
import json
|
2017-05-22 14:39:30 +01:00
|
|
|
from datetime import datetime, timedelta
|
2018-02-15 13:34:06 +00:00
|
|
|
from uuid import UUID
|
2016-11-09 14:56:54 +00:00
|
|
|
|
2017-05-24 16:27:15 +01:00
|
|
|
from iso8601 import iso8601, ParseError
|
2016-11-14 13:56:09 +00:00
|
|
|
from jsonschema import (Draft4Validator, ValidationError, FormatChecker)
|
2016-11-16 17:25:00 +00:00
|
|
|
from notifications_utils.recipients import (validate_phone_number, validate_email_address, InvalidPhoneError,
|
|
|
|
|
InvalidEmailError)
|
2016-10-25 14:53:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate(json_to_validate, schema):
|
2016-11-14 13:56:09 +00:00
|
|
|
format_checker = FormatChecker()
|
|
|
|
|
|
2018-02-15 13:34:06 +00:00
|
|
|
@format_checker.checks("validate_uuid", raises=Exception)
|
|
|
|
|
def validate_uuid(instance):
|
|
|
|
|
if isinstance(instance, str):
|
|
|
|
|
UUID(instance)
|
|
|
|
|
return True
|
|
|
|
|
|
2016-11-16 17:25:00 +00:00
|
|
|
@format_checker.checks('phone_number', raises=InvalidPhoneError)
|
2016-11-14 13:56:09 +00:00
|
|
|
def validate_schema_phone_number(instance):
|
2017-05-10 11:04:12 +01:00
|
|
|
if isinstance(instance, str):
|
2017-04-26 15:56:45 +01:00
|
|
|
validate_phone_number(instance, international=True)
|
2016-11-16 17:25:00 +00:00
|
|
|
return True
|
2016-11-14 13:56:09 +00:00
|
|
|
|
2016-11-16 17:25:00 +00:00
|
|
|
@format_checker.checks('email_address', raises=InvalidEmailError)
|
2016-11-14 13:56:09 +00:00
|
|
|
def validate_schema_email_address(instance):
|
2017-05-10 11:04:12 +01:00
|
|
|
if isinstance(instance, str):
|
2016-11-21 15:42:17 +00:00
|
|
|
validate_email_address(instance)
|
2016-11-16 17:25:00 +00:00
|
|
|
return True
|
2016-11-14 13:56:09 +00:00
|
|
|
|
2018-06-12 12:18:33 +01:00
|
|
|
@format_checker.checks('datetime_within_next_day', raises=ValidationError)
|
2017-05-17 15:06:15 +01:00
|
|
|
def validate_schema_date_with_hour(instance):
|
2017-05-15 15:02:38 +01:00
|
|
|
if isinstance(instance, str):
|
|
|
|
|
try:
|
2017-05-24 16:27:15 +01:00
|
|
|
dt = iso8601.parse_date(instance).replace(tzinfo=None)
|
2017-05-22 14:39:30 +01:00
|
|
|
if dt < datetime.utcnow():
|
|
|
|
|
raise ValidationError("datetime can not be in the past")
|
|
|
|
|
if dt > datetime.utcnow() + timedelta(hours=24):
|
|
|
|
|
raise ValidationError("datetime can only be 24 hours in the future")
|
2017-05-24 16:27:15 +01:00
|
|
|
except ParseError:
|
|
|
|
|
raise ValidationError("datetime format is invalid. It must be a valid ISO8601 date time format, "
|
|
|
|
|
"https://en.wikipedia.org/wiki/ISO_8601")
|
2017-05-15 15:02:38 +01:00
|
|
|
return True
|
|
|
|
|
|
2016-11-14 13:56:09 +00:00
|
|
|
validator = Draft4Validator(schema, format_checker=format_checker)
|
2016-10-25 14:53:31 +01:00
|
|
|
errors = list(validator.iter_errors(json_to_validate))
|
|
|
|
|
if errors.__len__() > 0:
|
2016-11-16 17:25:00 +00:00
|
|
|
raise ValidationError(build_error_message(errors))
|
2016-10-25 14:53:31 +01:00
|
|
|
return json_to_validate
|
|
|
|
|
|
|
|
|
|
|
2016-11-16 17:25:00 +00:00
|
|
|
def build_error_message(errors):
|
2016-10-25 14:53:31 +01:00
|
|
|
fields = []
|
|
|
|
|
for e in errors:
|
2017-01-09 16:22:27 +00:00
|
|
|
field = (
|
|
|
|
|
"{} {}".format(e.path[0], e.schema['validationMessage'])
|
|
|
|
|
if 'validationMessage' in e.schema else __format_message(e)
|
|
|
|
|
)
|
2016-11-16 17:25:00 +00:00
|
|
|
fields.append({"error": "ValidationError", "message": field})
|
2016-10-25 14:53:31 +01:00
|
|
|
message = {
|
2016-11-02 14:58:39 +00:00
|
|
|
"status_code": 400,
|
2017-06-15 11:32:51 +01:00
|
|
|
"errors": unique_errors(fields)
|
2016-10-25 14:53:31 +01:00
|
|
|
}
|
|
|
|
|
|
2016-10-25 18:04:03 +01:00
|
|
|
return json.dumps(message)
|
2016-11-16 17:25:00 +00:00
|
|
|
|
|
|
|
|
|
2017-06-15 11:32:51 +01:00
|
|
|
def unique_errors(dups):
|
|
|
|
|
unique = []
|
|
|
|
|
for x in dups:
|
|
|
|
|
if x not in unique:
|
|
|
|
|
unique.append(x)
|
|
|
|
|
return unique
|
|
|
|
|
|
|
|
|
|
|
2016-11-16 17:25:00 +00:00
|
|
|
def __format_message(e):
|
2016-12-15 16:35:27 +00:00
|
|
|
def get_path(e):
|
|
|
|
|
error_path = None
|
|
|
|
|
try:
|
2016-12-16 12:41:19 +00:00
|
|
|
error_path = e.path.popleft()
|
|
|
|
|
# no need to catch IndexError exception explicity as
|
|
|
|
|
# error_path is None if e.path has no items
|
2016-12-15 16:35:27 +00:00
|
|
|
finally:
|
|
|
|
|
return error_path
|
|
|
|
|
|
|
|
|
|
def get_error_message(e):
|
2017-01-09 16:22:27 +00:00
|
|
|
# e.cause is an exception (such as InvalidPhoneError). if it's not present it was a standard jsonschema error
|
|
|
|
|
# such as a required field not being present
|
|
|
|
|
error_message = str(e.cause) if e.cause else e.message
|
|
|
|
|
return error_message.replace("'", '')
|
2016-12-15 16:35:27 +00:00
|
|
|
|
|
|
|
|
path = get_path(e)
|
|
|
|
|
message = get_error_message(e)
|
|
|
|
|
if path:
|
|
|
|
|
return "{} {}".format(path, message)
|
|
|
|
|
else:
|
|
|
|
|
return "{}".format(message)
|