Render server validation errors as form errors - #421
Conversation
Mbin rejects magazine names that fall outside /^[a-zA-Z0-9_]{2,25}$/, but
the create-community form only disabled submit on an empty name, so an
invalid name failed server-side with no guidance.
- Add mbin_community_name.dart with pure, tested helpers to validate a
name, describe why it is invalid, and derive a sanitized suggestion.
- Give the shared TextEditor helperText/errorText support.
- In CommunityOwnerPanelGeneral (creation only), on Mbin: show the rule as
helper text, an inline error while the name is invalid, a one-tap
"Use suggestion" action, and keep submit disabled until the name is
valid. Lemmy/PieFed behaviour is unchanged.
- Add flutter_test dev dependency and focused unit tests for the helpers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7b7QotawaBJQ4isQKd14x
|
@jwr1 could you take a look when you have a chance? This turns the Mbin 400 name-validation response into an inline field error instead of the raw-JSON snackbar. I couldn't run |
Mbin rejects some community names for reasons the client cannot predict
(already taken, reserved, instance policy) with a 400 whose RFC 7807-style
body carries a human-readable `detail`. `checkResponseSuccess` embedded that
whole JSON body in an `http.ClientException` message, and the create screen
had no try/catch, so it surfaced through the global handler as a snackbar
full of raw JSON.
- Add `ServerErrorException`, thrown by `checkResponseSuccess` when an error
body is a JSON object with a string `title`/`detail`. Its `toString()`
returns `detail`/`title`, so every existing generic `catch (e)` renders a
clean message instead of raw JSON. Non-structured bodies (Lemmy/PieFed
`{"error": ...}`, HTML, empty) still throw `ClientException` unchanged, and
no call site matches that type.
- Community create screen: catch `ServerErrorException` from `create()`; a 400
carrying a `detail` is shown on the Name field through the existing
`errorText` plumbing and swallowed, so no snackbar fires. Anything else is
rethrown to the existing generic handling.
- The message is tracked alongside the name it was returned for, so it clears
itself on any edit, including the suggestion button setting the controller
text directly.
This is a backstop behind the client-side validation, which already blocks
predictable malformed names before submit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015icyWnYYmR7J8oSHocta6U
5e79981 to
8c71089
Compare
|
Restructured this PR: it was independently duplicating #420's client-side validation and |
Problem
#420 stops predictable bad names — anything outside Mbin's
^[a-zA-Z0-9_]{2,25}$— before the request is ever sent. But the server stillrejects names for reasons a client cannot know in advance: already taken,
reserved, or blocked by instance policy. Those come back as
400with anRFC 7807-style body:
{ "type": "https://tools.ietf.org/html/rfc2616#section-10", "title": "An error occurred", "status": 400, "detail": "..." }ServerClient.checkResponseSuccessembedded that entire JSON body into anhttp.ClientExceptionmessage, and the create screen had notry/catch, soit reached the global handler in
main.dartand rendered as a snackbar full ofraw JSON. The useful
detailwas buried inside it.So: #420 handles malformed names before submit; this PR makes the server's own
validation responses render as form errors instead of raw
ClientException/JSON. It's the backstop, not the primary fix.Changes
lib/src/api/client.dartServerErrorExceptioncarryingstatusCode/uri/title/detail/rawBody, withtoString()returningdetail ?? title ?? <generic>.checkResponseSuccessnow tries to parse an error body as a JSON object witha string
title/detailand throwsServerErrorExceptionwhen it can.Bodies that aren't structured problem JSON — Lemmy/PieFed
{"error": ...},HTML, empty — still throw
http.ClientExceptionexactly as before. No callsite in the repo matches
ClientExceptionby type, so this is backwardscompatible and incidentally means every generic
catch (e)now shows a cleanmessage instead of a JSON blob.
lib/src/screens/explore/community_owner_panel.dartcreate()is wrapped intry / on ServerErrorException: a400carrying adetailis shown on the Name field through theerrorTextplumbing added inValidate Mbin community names when creating a community #420, and swallowed so no snackbar fires. Anything else is rethrown to the
existing generic handling.
while the field still holds that name, so it clears itself on any edit —
including Validate Mbin community names when creating a community #420's suggestion button, which sets the controller text directly
and so never fires
onChanged.if/elseso only the create call iswrapped; the edit path is behaviourally unchanged.
This PR deliberately contains no validation, helper-text, suggestion, or
TextEditorchanges — all of that is #420's.Testing
No Flutter toolchain in this environment, so
dart format/flutter test/the app itself were not run locally — CI is the first real check. Verified by
hand: the sample body above parses with
detailas aString, and no line inthe changed files exceeds 80 columns.
Manual test plan (Mbin account), after #420:
the server's
detailappears under the Name field, no snackbar.also clears.
🤖 Generated with Claude Code
https://claude.ai/code/session_015icyWnYYmR7J8oSHocta6U