Skip to content

fix(forms): an optional field with a format constraint must accept empty - #6690

Open
delchev wants to merge 1 commit into
masterfrom
fix/optional-field-format-accepts-empty
Open

fix(forms): an optional field with a format constraint must accept empty#6690
delchev wants to merge 1 commit into
masterfrom
fix/optional-field-format-accepts-empty

Conversation

@delchev

@delchev delchev commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What

A blank optional field cannot be saved.

Creating a Customer with only the required Name fails with "Could not save — Fields that need attention: Email" / "Enter a valid email address.", even though Email is not required. The only way through is to invent an address — which then pollutes the data, since the dunning and notify paths mail whatever is stored there.

Cause

Both layers applied the format rule to the empty value:

  • Client (application-core formValidation.js) ran every rule regardless of presence, so the email rule on an optional field rejected ''.
  • Server (EntityController.java.template) guarded the pattern on != null only, so a direct REST caller sending "" got a 400. The Harmonia form already posts '' as null, so this path is REST-only.

A format constraint describes what a value must look like when there is one. Presence is required's decision alone; applying a format rule to an empty value makes every optional field carrying one required by the back door.

Fix

Client — skip any non-required rule when the value is blank. This covers email / pattern / minLength / min / max alike. No required field loses its check: the generator emits either the required rule or the format rule for a given field, never both.

This file is the shared runtime, so every generated app picks the fix up without a regen.

Server — emit the blank skip only for optional properties:

// optional + pattern
if (entity.Email != null && !entity.Email.toString().isBlank() && !entity.Email.toString().matches(...))
// required + pattern — unchanged
if (entity.Code != null && !entity.Code.toString().matches(...))

A required property keeps the strict check, where blank is not a legal value — so nothing that is rejected today starts passing. Modules pick this up on their next regen.

Verification

  • The client validator against 11 cases: blank / null / whitespace accepted on an optional email; "abc" still refused; required still fails on '' and null; optional minLength/min skip empty but still fail a present bad value.
  • The controller template rendered through a real Velocity engine with an optional-patterned and a required-patterned property, confirming the emitted Java gains && !isBlank() on the optional one only, and is byte-identical for the required one.

🤖 Generated with Claude Code

A blank optional field could not be saved. Creating a Customer with only the
required Name failed with "Could not save - Fields that need attention: Email"
/ "Enter a valid email address.", even though Email is not required. The only
way through was to invent an address, which then pollutes the data - the
dunning and notify paths mail whatever is stored there.

Both layers applied the format rule to the empty value:

- the shared client validator ran every rule regardless of presence, so the
  `email` rule on an OPTIONAL field rejected '' (a field gets EITHER the
  `required` rule OR the format rule, never both, so no required field loses
  its check);
- the generated controller guarded the pattern on `!= null` only, so a direct
  REST caller sending "" got a 400 (the Harmonia form already posts '' as
  null, so this path is REST-only).

A format constraint describes what a value must look like WHEN THERE IS ONE.
Presence is `required`'s decision alone; applying a format rule to an empty
value makes every optional field carrying one required by the back door.

Client: skip any non-`required` rule when the value is blank - this covers
email/pattern/minLength/min/max alike. The file is the SHARED runtime
(application-core), so every generated app picks it up without a regen.

Server: emit the blank skip only for OPTIONAL properties. A required property
keeps the strict check, where blank is not a legal value - so nothing that is
rejected today starts passing.

Verified: the client validator against 11 cases (blank/null/whitespace accepted
on an optional email, "abc" still refused, required still fails on '' and null,
optional minLength/min skip empty but still fail a present bad value); the
controller template rendered through Velocity with an optional-and-a
required-patterned property, confirming the emitted Java gains `&& !isBlank()`
on the optional one only and is unchanged for the required one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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