Skip to content

[FEATURE] Enforce squad number immutability on PUT #529

@nanotaboada

Description

@nanotaboada

Problem

On PUT, `squad_number` is included in the request body but silently discarded
— the service always overwrites it with the path parameter value
(`player.squad_number = squad_number` in `services/player_service.py`).
Clients receive no feedback when the body value conflicts with the URL, which
is misleading: a request that appears well-formed is quietly modified
server-side with no indication that the body value was ignored.

Proposed Solution (minimal fix)

Add a mismatch guard at the top of `put_async` in `routes/player_route.py`,
before the existence lookup:

```python
if player_model.squad_number != squad_number:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
```

The path parameter is the authoritative source of identity on PUT. If the body
carries a different value the request is ambiguous — rejecting it with 400 is
the correct and explicit behaviour.

Alternatives Considered

Option A — Split into PlayerCreateModel / PlayerUpdateModel

Splitting the request model adds a third Pydantic model to the codebase.
The model itself does not need to know about the difference between POST and
PUT — that distinction belongs at the route layer, where HTTP concerns are
handled. A route-level check is simpler and keeps the model layer focused on
data shape, not operation semantics.

Option B — PlayerUpdateModel with Optional squad_number

Making `squad_number` optional on an update model is cleaner from a pure REST
perspective (the URL already conveys identity), but it silently accepts a
mismatched value from the client rather than rejecting it — which is the
original problem. This option trades one form of silent behaviour for another.

Option C — Pydantic model_validator with context

Pydantic v2 supports `model_validate(data, context=...)` for per-operation
validation rules. However, FastAPI validates `Body(...)` automatically and
does not expose a way to inject context into that pipeline without bypassing
framework conventions. The added complexity yields no benefit over a simple
route-level check.

Acceptance Criteria

  • PUT returns 400 if `squad_number` in the body does not match the path parameter
  • `PlayerRequestModel` remains unchanged (single model for POST and PUT)
  • New test `test_request_put_player_squadnumber_mismatch_response_status_bad_request` covers the mismatch case
  • All pre-commit checks pass (flake8, black, pytest, coverage ≥ 80%)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority:highImportant for production readiness. Schedule for current milestone.pythonPull requests that update Python code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions