fix(members): let a populated custom field be cleared again - #68
Merged
Conversation
Emptying a custom field on a member did nothing: the save failed and the old
value stayed.
Clearing is expressed as `value: null` on PUT /v1/members/{id}/fields, which
the server upserts as a null rather than deleting the row. `value` is required
on every entry; there is no "omit to leave this one alone" mode. Moshi omits
null fields by default, so a clear serialised to
[{"field_id": "..."}]
with no `value` at all. That is not a smaller request, it is an invalid one,
and the server rejected it. Verified both halves: the serialisation by probing
the adapter, and the rejection against the server's own Pydantic, which raises
a validation error for a missing `value` and accepts an explicit null.
Adds a hand-written adapter that always writes `value`, null included. Same
trap the fronts PATCH hit, from the other side: there the fix was to let an
explicit null through, here it is to stop dropping one.
Tests cover the cleared entry, a set entry, non-string value types, and a
mixed batch that clears one field while setting another in the same save. One
of them builds the app's own Moshi rather than a local one, so forgetting the
registration fails a test instead of quietly restoring the bug.
Worth doing on the server too, as defence for any other client whose serialiser
omits nulls: accepting a missing `value` as null costs nothing, since the
endpoint has no other meaning for its absence. That is not a substitute for
this, though, since it would only help once an instance upgrades.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Emptying a custom field on a member did nothing: the save failed and the old value stayed.
It's the client, not the backend
Clearing is expressed as
value: nullonPUT /v1/members/{id}/fields. The server upserts that null rather than deleting the row, andvalueis required on every entry - there is no "omit to leave this one alone" mode.Moshi omits null fields by default, so emptying a text field (which stages
nullviait.ifBlank { null }) serialised to:[{"field_id": "..."}]No
valuekey at all. That isn't a weaker request, it's an invalid one.Both halves verified rather than reasoned about:
PROBE-CLEAR: [{"field_id":"f1"}]vsPROBE-SET: [{"field_id":"f1","value":"hi"}].valueraises aValidationError, an explicit null is accepted and stores as null.The fix
A hand-written adapter that always writes
value, null included. This is the same trap the fronts PATCH hit, approached from the other side:FrontUpdateJsonAdapterexists to let an explicit null through, this one exists to stop one being dropped.Registered via a factory, since the adapter needs the
Moshiinstance to delegate type-erased values (a field value can be a string, number, boolean, or a list for multiselect).Tests
7, covering the cleared entry, a set entry, non-string types, and a mixed batch that clears one field while setting another in the same save - which is what a real edit looks like.
One test deliberately builds the app's own Moshi via
NetworkModule.provideMoshi()rather than a local one. Without it, forgetting the registration would leave every other test passing while clearing stayed broken in the app.Also worth doing on the server, separately
Accepting a missing
valueas null (value: Any = None) would protect any other client whose serialiser omits nulls - Moshi does, Go'somitemptydoes, various JS paths do. It costs nothing, since absence has no other meaning on this endpoint.It is not a substitute for this fix: a server-side change only helps once an instance upgrades, and self-hosters move on their own schedule. Same reasoning as the replace-endpoint fallback in #59. Filing that separately against the server repo.
Testing
:app:assemblePlayRelease+:app:testPlayReleaseUnitTestgreen. Device checklist: