mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-16 11:12:21 +03:00
Use a custom dictionary class to safely skip substitution errors in ValidationError messages.
"%" substitution requires all keys to be matched during substitution, so if a ValidationError happens to include a %(foo)s style variable not met by parameters, it will throw a KeyError. In Field.run_validators there is also an accumulation of errors that are wrapped by a final ValidationError which can then throw TypeError if any of the sub-errors contain replaceable substrings. This patch implements a subclassed dict which simply returns the key's name for any missing keys. The end result for the logic in exceptions.py is that the final message is an exact copy of the original message with only found parameters replaced and the rest left untouched. Signed-off-by: James Tanner <tanner.jc@gmail.com>
This commit is contained in:
parent
77ef27f18f
commit
93b677cbb6
|
@ -15,6 +15,11 @@ from rest_framework import status
|
|||
from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
|
||||
|
||||
|
||||
class SafeReplacerDict(dict):
|
||||
def __missing__(self, key):
|
||||
return key
|
||||
|
||||
|
||||
def _get_error_details(data, default_code=None):
|
||||
"""
|
||||
Descend into a nested data structure, forcing any
|
||||
|
@ -144,7 +149,7 @@ class ValidationError(APIException):
|
|||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = _('Invalid input.')
|
||||
default_code = 'invalid'
|
||||
default_params = {}
|
||||
default_params = SafeReplacerDict()
|
||||
|
||||
def __init__(self, detail=None, code=None, params=None):
|
||||
if detail is None:
|
||||
|
@ -157,6 +162,7 @@ class ValidationError(APIException):
|
|||
# For validation failures, we may collect many errors together,
|
||||
# so the details should always be coerced to a list if not already.
|
||||
if isinstance(detail, str):
|
||||
#import pdb; pdb.set_trace()
|
||||
detail = [detail % params]
|
||||
elif isinstance(detail, ValidationError):
|
||||
detail = detail.detail
|
||||
|
@ -166,6 +172,7 @@ class ValidationError(APIException):
|
|||
if isinstance(detail_item, ValidationError):
|
||||
final_detail += detail_item.detail
|
||||
else:
|
||||
#import pdb; pdb.set_trace()
|
||||
final_detail += [detail_item % params if isinstance(detail_item, str) else detail_item]
|
||||
detail = final_detail
|
||||
elif not isinstance(detail, dict) and not isinstance(detail, list):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import pytest
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework import serializers, status
|
||||
|
@ -195,3 +196,20 @@ class TestValidationErrorWithDjangoStyle(TestCase):
|
|||
assert str(error.detail[1]) == 'Invalid value: 43'
|
||||
assert str(error.detail[2]) == 'Invalid value: 44'
|
||||
assert str(error.detail[3]) == 'Invalid value: 45'
|
||||
|
||||
def test_validation_error_without_params(self):
|
||||
"""Ensure that substitutable errors can be emitted without params."""
|
||||
|
||||
# mimic the logic in fields.Field.run_validators by saving the exception
|
||||
# detail into a list which will then be the detail for a new ValidationError.
|
||||
# this should not throw a KeyError or a TypeError even though
|
||||
# the string has a substitutable substring ...
|
||||
errors = []
|
||||
try:
|
||||
raise ValidationError('%(user)s')
|
||||
except ValidationError as exc:
|
||||
errors.extend(exc.detail)
|
||||
|
||||
# ensure it raises the correct exception type as an input to a new ValidationError
|
||||
with pytest.raises(ValidationError):
|
||||
raise ValidationError(errors)
|
||||
|
|
Loading…
Reference in New Issue
Block a user