Cloud-CV / Cloud-CV/EvalAI

Malformed parts JSON returns HTTP 500 instead of 400 in multipart upload completion - apps/jobs/views.py:3208

Open
#5,222 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
2k
Forks
983
Avg merge
2h 54m
Merged PRs (30d)
14

Description

Sending a multipart upload completion request with malformed `parts` JSON returns HTTP 500 instead of HTTP 400.

## Offending code

Both multipart-completion endpoints validate that `parts` is *present*, then parse it with a bare `json.loads()`:

https://github.com/Cloud-CV/EvalAI/blob/f8aff86a7e613c61fae3e4b0e5c0382fe3b52a25/apps/jobs/views.py#L3200-L3208

```python
if request.data.get("parts") is None:
response_data = {"error": "Uploaded file Parts metadata is missing"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)

if request.data.get("upload_id") is None:
response_data = {"error": "Uploaded file UploadId is missing"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)

file_parts = json.loads(request.data["parts"])
```

The same pattern is in `finish_annotation_file_upload()`:

https://github.com/Cloud-CV/EvalAI/blob/f8aff86a7e613c61fae3e4b0e5c0382fe3b52a25/apps/challenges/views.py#L4340

## Why it is wrong

That `json.loads()` call sits outside every `try` block in the view — in `finish_submission_file_upload()` the `try` only begins on the following line, around `get_submission_model()`.

`json.JSONDecodeError` subclasses `ValueError`, not `APIException`. DRF's `exception_handler` returns `None` for it:

```
>>> from rest_framework.views import exception_handler
>>> import json
>>> try: json.loads("not-json")
... except Exception as e: exc = e
>>> exception_handler(exc, {})
None
```

Returning `None` means DRF re-raises, the exception propagates to Django, and the request is answered with HTTP 500.

So a *missing* `parts` key is treated as a client error (400), while *malformed* `parts` — the same class of bad client input — is treated as a server fault (500).

This is inconsistent with how the codebase handles the identical situation elsewhere. `update_leaderboard_data()` in the same file guards its parse and returns 400:

https://github.com/Cloud-CV/EvalAI/blob/f8aff86a7e613c61fae3e4b0e5c0382fe3b52a25/apps/jobs/views.py#L2713-L2720

## What the user sees

- A client that sends a truncated or otherwise malformed `parts` payload gets an opaque `500 Internal Server Error` with no indication of what was wrong with the request.
- The traceback is reported to Sentry as a server fault, adding noise that hides real server errors.
- CLI/SDK clients cannot distinguish "fix your request" from "the server is broken", so they have no reason to stop retrying a request that will never succeed.

## Steps to reproduce

1. Authenticate as a participant with a submission in a challenge phase.
2. `POST` to `/api/jobs/phases//finish_submission_file_upload//` with:
```
parts=not-valid-json
upload_id=some-upload-id
```
3. The response is HTTP 500 with an unhandled `json.decoder.JSONDecodeError`.

Sending no `parts` key at all correctly returns HTTP 400, which shows the divergence.

## Expected behaviour

- Malformed `parts` JSON returns HTTP 400.
- The response body names `parts` as the offending field.
- No unhandled exception is raised, so the failure is not reported to Sentry as a server error.
- `finish_submission_file_upload()` and `finish_annotation_file_upload()` behave the same way.

---

I have a branch that guards both call sites using the same pattern as `update_leaderboard_data()` and adds a regression test; I'll open a PR referencing this issue.

Contributor guide

Open the contributing guide

Research direction

Start in apps/jobs/views.py around finish_submission_file_upload() and update_leaderboard_data(), then inspect apps/challenges/views.py around finish_annotation_file_upload(). Add regression coverage for malformed parts input and verify both endpoints return HTTP 400 naming parts without an unhandled exception.

Written by the indexing model from the issue text.

Assessment

Tech stack
django, python
Domain
api, backend, testing
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.