PostHog / drf-exceptions-hog

Standardized and easy-to-parse API error responses for Django REST Framework (DRF).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

incorect behavior when validating a list

abtinmo opened this issue · comments

commented

when DRF throws ValidationError for elements inside of a list, package populates code and detail fields with not usable data.

example:

models:

class Day(models.Model):
    name = models.CharField(max_length=50)


class Time(models.Model):
    day = models.ForeignKey(Day, on_delete=models.CASCADE, related_name="times")
    time = models.TimeField()

serializers

class TimeSerializer(serializers.ModelSerializer):
    day = serializers.IntegerField(required=False)

    class Meta:
        fields = "__all__"
        model = Time


class DaySerializer(serializers.ModelSerializer):
    times = TimeSerializer(many=True)

    class Meta:
        fields = "__all__"
        model = Day

    def create(self, validated_data):
        # handle nested creation
        # . . .

request body:


{
    "times": [{}],
    "name": ""
}

response:

{
    "type": "validation_error",
    "code": "{'time': ['required']}",
    "detail": "{'time': [ErrorDetail(string='This field is required.', code='required')]}",
    "attr": "times"
}

list response

{
    "type": "multiple",
    "code": "multiple",
    "detail": "Multiple exceptions ocurred. Please check list for details.",
    "attr": null,
    "list": [
        {
            "type": "validation_error",
            "code": "{'time': ['required']}",
            "detail": "{'time': [ErrorDetail(string='This field is required.', code='required')]}",
            "attr": "times"
        },
        {
            "type": "validation_error",
            "code": "blank",
            "detail": "This field may not be blank.",
            "attr": "name"
        }
    ]

I'm having the same problem.
Error is generated when value is not a string on

# Handle nested attributes
if isinstance(exception_key, list):
for key in exception_key:
value = value[key]
return str(value if isinstance(value, str) else value[0])

An extremely naive solution to part of the problem:

            # Handle nested attributes
            if isinstance(exception_key, list):
                for key in exception_key:
                    value = value[key]
            if isinstance(value, list):
                for item in value:
                    value = item
            if isinstance(value, dict):
                for key in value:
                    value = value[key]

            return str(value if isinstance(value, str) else value[0])

Thanks for reporting @abtinmo & the proposed solution @luzfcb, will take a look in the next few days to push a fix.

I think its related to this issue and This error started to occur in some of the plugins we use

How to fix this situation?

image

Error Trace

ValueError: not enough values to unpack (expected 2, got 1)
  File "jwt/api_jws.py", line 191, in _load
    header_segment, payload_segment = signing_input.split(b".", 1)
DecodeError: Not enough segments
  File "rest_framework_simplejwt/backends.py", line 90, in decode
    return jwt.decode(
  File "jwt/api_jwt.py", line 119, in decode
    decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs)
  File "jwt/api_jwt.py", line 90, in decode_complete
    decoded = api_jws.decode_complete(
  File "jwt/api_jws.py", line 149, in decode_complete
    payload, signing_input, header, signature = self._load(jwt)
  File "jwt/api_jws.py", line 193, in _load
    raise DecodeError("Not enough segments") from err
TokenBackendError: Token is invalid or expired
  File "rest_framework_simplejwt/tokens.py", line 43, in __init__
    self.payload = token_backend.decode(token, verify=verify)
  File "rest_framework_simplejwt/backends.py", line 105, in decode
    raise TokenBackendError(_('Token is invalid or expired'))
TokenError: Token is invalid or expired
  File "rest_framework_simplejwt/views.py", line 27, in post
    serializer.is_valid(raise_exception=True)
  File "rest_framework/serializers.py", line 227, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "rest_framework/serializers.py", line 429, in run_validation
    value = self.validate(value)
  File "rest_framework_simplejwt/serializers.py", line 104, in validate
    refresh = RefreshToken(attrs['refresh'])
  File "rest_framework_simplejwt/tokens.py", line 45, in __init__
    raise TokenError(_('Token is invalid or expired'))
InvalidToken: {'detail': ErrorDetail(string='Token is invalid or expired', code='token_not_valid'), 'code': ErrorDetail(string='token_not_valid', code='token_not_valid')}
  File "rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "rest_framework_simplejwt/views.py", line 29, in post
    raise InvalidToken(e.args[0])
KeyError: 0
  File "django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "contextlib.py", line 79, in inner
    return func(*args, **kwds)
  File "django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "rest_framework/views.py", line 466, in handle_exception
    response = exception_handler(exc, context)
  File "exceptions_hog/handler.py", line 295, in exception_handler
    detail=_get_detail(exc, exception_list[0][1]),
  File "exceptions_hog/utils.py", line 9, in function_wrapper
    return_value: Any = func(*args, **kwargs)
  File "exceptions_hog/handler.py", line 172, in _get_detail
    return str(value if isinstance(value, str) else value[0])

Hey @abtinmo @luzfcb @mertcangokgoz, I attempted to solve this in #29. Added a test case for what you described above, but tests seem to pass as expected. I'm probably missing something on how your exceptions are being thrown. It would be a great help if you could write a test case on test_handler.py to simulate the exact conditions that are leading to the exception, and I can then fix the code to get tests passing.

Alternatively, if you can take a look at #29 and let me know if I'm missing something related to the test case.

@paolodamico thank you for your support.

I'll do another test tomorrow about the problem, I don't think so, but there may be a problem in the code we developed. This problem may be caused by https://github.com/jazzband/djangorestframework-simplejwt, which we use for user logins on django.

If you want to test it, please do a little test with https://github.com/jazzband/djangorestframework-simplejwt.