Skip to content

fix(api): let optional fields be cleared on member, group and system edits - #70

Open
SiteRelEnby wants to merge 1 commit into
mainfrom
fix/clearable-patch-fields
Open

fix(api): let optional fields be cleared on member, group and system edits#70
SiteRelEnby wants to merge 1 commit into
mainfrom
fix/clearable-patch-fields

Conversation

@SiteRelEnby

Copy link
Copy Markdown
Collaborator

Found by auditing for the failure mode behind #68. It was not a one-off.

What was broken

Emptying an optional field and saving did nothing - the old value came back.

where fields
Member display name, pronouns, colour, birthday, description
Group description, colour
System profile description, tag, colour
Both removing an avatar or banner

Pronouns is probably the one people noticed; avatar removal is the one that looks most like the app ignoring you.

Why

These endpoints read the body with exclude_unset, so presence is the contract:

  • omitted -> leave it alone
  • JSON null -> clear it
  • a value -> set it

Moshi omits null fields, which expresses the first two-thirds and leaves no way to clear anything. The ViewModels already produce null for an emptied field; it just never reached the wire.

Probed rather than assumed - a member edit with five cleared fields serialised to:

{"name":"Alex"}

The server then correctly left everything alone.

Why not just turn on null serialisation

That was the tempting one-liner, and it would have been worse. Each of these bodies also carries fields backed by NOT NULL columns that the server explicitly rejects an explicit null for - a member's name and privacy, a system's date_format, privacy, show_member_created_date and the front defaults. Blanket nulls would start sending those and fail the entire save rather than one field.

So each field is either clearable or omit-when-null, matching the server's _reject_explicit_null lists. The two helpers are named clears(...) and omitsWhenNull(...) so a field added later has to make the choice rather than inheriting a default.

No ViewModel changes

The call sites already do takeIf { it.isNotBlank() }, producing null for an emptied field. That now means what it always should have.

Follow-up when #69 lands

#69 adds emoji to MemberUpdate and works around this by sending an empty string. Once both are in, add clears("emoji", ...) to the member adapter and simplify that workaround to a plain null. There's a comment at the spot so it isn't lost. Nothing breaks in either merge order - the workaround keeps working regardless.

Testing

11 unit tests, built through the app's own Moshi via NetworkModule.provideMoshi() so a missing registration fails here rather than only in the field. They pin both directions: fields that must clear, and fields that must stay absent because a null would fail the save.

:app:assemblePlayRelease, :app:assembleOpenRelease, :app:testPlayReleaseUnitTest green.

Device checklist:

  • Member: set pronouns, save, reopen, clear them, save - they stay gone
  • Same for display name, birthday, description, colour
  • Remove a member's avatar, and a member's banner: both go and stay gone
  • Group: clear description and colour
  • Settings > Profile: clear the system tag and description, remove the system avatar
  • A normal edit that changes one field leaves the others alone
  • Renaming a member still works (the NOT NULL path)

…edits

Emptying an optional field and saving did nothing: the old value came back.
This affected a member's display name, pronouns, colour, birthday and
description; a group's description and colour; and a system's description, tag
and colour. Removing an avatar or banner was broken the same way, on both a
member and the system profile.

These endpoints read the body with exclude_unset, so presence is the contract:
omitted leaves a field alone, an explicit null clears it. Moshi omits null
fields, so a cleared field was simply absent from the request and the server
correctly left it as it was. Confirmed by probing the serialiser, which turned
a member edit with five cleared fields into {"name":"Alex"}.

The obvious shortcut does not work. Turning on null serialisation for these
bodies would also null the fields backed by NOT NULL columns that the server
explicitly rejects an explicit null for, failing the whole save instead of one
field. So each field is either clearable or omit-when-null, matching the
server's _reject_explicit_null lists, and the two helpers are named after which
is which so a field added later has to make the choice.

No ViewModel changes: they already produce null for an emptied field, which now
means what it always should have.

Found by auditing for the failure mode behind the custom-field-value fix. That
one is the same bug in a different shape, where the server requires the key
rather than treating its absence as unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant