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