Skip to content

fix(api): handle non-object payloads in edge identity update-traits#7952

Open
zain-asif-dev wants to merge 1 commit into
Flagsmith:mainfrom
zain-asif-dev:fix/edge-identity-update-traits-list-payload
Open

fix(api): handle non-object payloads in edge identity update-traits#7952
zain-asif-dev wants to merge 1 commit into
Flagsmith:mainfrom
zain-asif-dev:fix/edge-identity-update-traits-list-payload

Conversation

@zain-asif-dev

Copy link
Copy Markdown
Contributor
  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Closes #7951

The edge identity update-traits endpoint raised an unhandled 500 error when the request body was a JSON array instead of an object, because TraitModel(**request.data) raises a bare TypeError for non-mapping input, which was not caught by the existing pydantic.ValidationError handler.

Switching to TraitModel.model_validate(request.data) correctly turns that into a pydantic.ValidationError, but doing so uncovers a second issue: pyngo.drf_error_details() assumes every error in ValidationError.errors() has a non-empty loc, which isn't true for a top-level model_type error (the loc is () when the payload itself isn't an object). That call then raises an IndexError, so the 500 doesn't go away, it just changes shape.

This PR rejects non-mapping payloads up front with a normal DRF ValidationError (400), before either of those code paths is reached, and switches the remaining case over to model_validate for more consistent validation behavior.

How did you test this code?

Added a regression test (test_edge_identities_update_trait__malformed_traits_payload__returns_400) that PUTs a JSON array to the update-traits endpoint and asserts a 400 response, with no DynamoDB write attempted. Verified it fails against the old code (500) and passes with the fix. Also ran the full tests/integration/edge_api/identities/test_edge_identity_viewset.py suite (22 passed), ruff check/ruff format --check, and mypy on the changed file.

TraitModel(**request.data) raised an uncaught TypeError when request.data was a list instead of a dict, resulting in an unhandled 500 response. Switching to TraitModel.model_validate() surfaces a ValidationError instead, but pyngo.drf_error_details() then crashes with an IndexError because the top-level model_type error it produces has an empty loc tuple. Reject non-mapping payloads explicitly before validation so callers get a proper 400 response.

Signed-off-by: Zain Asif <zainasif.jutt1@gmail.com>
@zain-asif-dev zain-asif-dev requested a review from a team as a code owner July 6, 2026 18:37
@zain-asif-dev zain-asif-dev requested review from khvn26 and removed request for a team July 6, 2026 18:37
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

@zain-asif-dev is attempting to deploy a commit to the Flagsmith Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 45693c70-bac8-45db-868a-26ef3f9b387b

📥 Commits

Reviewing files that changed from the base of the PR and between da194ca and 4d529ca.

📒 Files selected for processing (2)
  • api/edge_api/identities/views.py
  • api/tests/integration/edge_api/identities/test_edge_identity_viewset.py

📝 Walkthrough

Walkthrough

The update_traits view now validates that the incoming request payload is a mapping before parsing it into a TraitModel, raising a DRF ValidationError with a non_field_errors message otherwise. The trait parsing method changes from direct construction (TraitModel(**request.data)) to TraitModel.model_validate(request.data), while existing pydantic error handling is preserved. A corresponding integration test verifies that a malformed (array) payload returns a 400 response without triggering a DynamoDB write.

Estimated code review effort: 2 (Simple) | ~10 minutes

Changes

File Summary
api/edge_api/identities/views.py Adds a mapping-type guard for request payload and switches to TraitModel.model_validate
api/tests/integration/edge_api/identities/test_edge_identity_viewset.py Adds integration test for malformed (array) traits payload returning 400

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant UpdateTraitsView
  participant TraitModel

  Client->>UpdateTraitsView: request.data
  UpdateTraitsView->>UpdateTraitsView: check request.data is mapping
  alt not a mapping
    UpdateTraitsView-->>Client: 400 ValidationError (non_field_errors)
  else is a mapping
    UpdateTraitsView->>TraitModel: model_validate(request.data)
    alt pydantic ValidationError
      TraitModel-->>UpdateTraitsView: ValidationError
      UpdateTraitsView-->>Client: 400 drf_error_details
    else valid
      TraitModel-->>UpdateTraitsView: trait instance
      UpdateTraitsView-->>Client: success response
    end
  end
Loading

Related issues: Not specified in the provided context.

Related PRs: Not specified in the provided context.

Suggested labels: bug, api

Suggested reviewers: Not specified in the provided context.

Poem

A rabbit checked the data shape,
before it let the trait escape.
No list, no stray, no odd surprise—
just mappings pass this careful guise.
A test confirms the guard holds tight,
four hundred sent, the code done right. 🐇


Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the api Issue related to the REST API label Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update-traits crash when traits malformed

1 participant