This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts-site.git
The following commit(s) were added to refs/heads/main by this push:
new cd473a2f8 WW-5702 Document the constraint provider's attribute hygiene
rules (#333)
cd473a2f8 is described below
commit cd473a2f8f70227c6782d122b91abc8a9528d654
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Sep 14 08:45:39 2026 +0200
WW-5702 Document the constraint provider's attribute hygiene rules (#333)
- data-msg-* is rendered only on controls that submit a value, and only
for validator types that are plain attribute names
- a provider cannot change type or override a developer-set attribute;
the type="email" example is replaced
- \- is accepted only inside a character class; []a] is rejected
- the visitor row was stale since the visited object's validators now
apply to nested fields
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
source/core-developers/client-side-validation.md | 37 ++++++++++++++++++------
1 file changed, 28 insertions(+), 9 deletions(-)
diff --git a/source/core-developers/client-side-validation.md
b/source/core-developers/client-side-validation.md
index e3a2949a5..eff723918 100644
--- a/source/core-developers/client-side-validation.md
+++ b/source/core-developers/client-side-validation.md
@@ -74,8 +74,9 @@ an `int` or `double` validator is attached to it.
| `double` | `min` / `max` | same as above; only inclusive bounds are emitted
— exclusive bounds have no HTML equivalent and are omitted |
| `date` | — | nothing yet; temporal `min`/`max` is deferred to a future
release |
| `email`, `url`, `creditcard` | — | never emitted |
-| `fieldexpression`, `expression`, `conversion`, visitor validators | — |
never emitted |
-| any validator carrying a message | `data-msg-<validatorType>` | always
added, including for validators that emit no constraint attribute at all |
+| `fieldexpression`, `expression`, `conversion` | — | never emitted |
+| `visitor` | — | nothing for the visitor itself; the visited object's own
validators apply to its nested fields (`user.name`) exactly as if they were
declared on the action |
+| any validator carrying a message | `data-msg-<validatorType>` | always added
on a control that submits a value, including for validators that emit no
constraint attribute at all; never on `<s:label>` or a control of an unknown
`type` |
Two of these conditions are easy to miss and sharply limit how often
`required`, `minlength`/`maxlength`,
and `pattern` actually show up:
@@ -103,8 +104,14 @@ validators are not. In practice, expect both to show up
rarely until application
**ECMAScript-safe** means the regex uses only constructs that mean the same
thing in Java's regex engine
and in the browser's: literals, `\d`/`\w` and their negations, character
classes without POSIX or Unicode
-property syntax, grouping, alternation, anchors, and bounded quantifiers.
Notably, **`\s` and `\S` are
-excluded** — Java's `\s` is ASCII-only by default while ECMAScript's `\s`
covers the wider Unicode
+property syntax, grouping, alternation, anchors, and bounded quantifiers.
Browsers compile `pattern` with
+the `v` (unicode sets) flag, which is stricter than Java inside a character
class: `( ) { } / |` must be
+escaped there, a hyphen is accepted only as a range operator between two plain
literals (`[a-z]`) or
+escaped (`[\w\-]`), doubled punctuators such as `..` or `!!` are reserved, and
a class starting with a
+literal `]` (`[]a]`) is rejected. Outside a class, `\-` is not a legal escape
and a lone `]` or `}` is
+an error, both of which Java reads as literals. A common email-shaped regex
like `[a-z0-9._%+-]+@` is
+therefore *not* safe (the trailing unescaped `-`); `[a-z0-9._%+\-]+@` is.
Notably,
+**`\s` and `\S` are excluded** — Java's `\s` is ASCII-only by default while
ECMAScript's `\s` covers the wider Unicode
whitespace set, so a pattern like `^\S+$` would accept a value containing a
non-breaking space server-side
and reject it in the browser. Any regex using a construct outside this
allowlist simply gets no `pattern`
attribute at all — it is never rejected loudly, it just quietly doesn't get a
client-side check.
@@ -118,6 +125,11 @@ They exist purely as a hook: an application can write its
own script to read `da
whichever messages it wants, in whatever way it wants, including for
validators (like `email` or
`creditcard`) that never get a native browser check.
+The hook is only rendered on controls that submit a value. `<s:label>` never
does, and neither does a text
+field whose `type` the framework does not recognise, so those carry no
`data-msg-*` at all. The validator
+type also becomes part of the attribute name, which HTML escaping does not
protect; a custom validator whose
+type is not a plain attribute name (letters, digits, `_` and `-`) gets no
message attribute.
+
### `requiredLabel` is unrelated to the `required` attribute
This is a common point of confusion: the `requiredLabel` tag attribute only
controls whether a visual
@@ -134,11 +146,18 @@ The mapping above is implemented by
`StrutsHtmlConstraintProvider`, the default
struts.htmlConstraintProvider=struts
```
-An application that wants a less conservative mapping — for example, treating
an `email` validator as
-`type="email"`, or emitting `pattern` for case-insensitive regexes by
rewriting them — can register its own
-`HtmlConstraintProvider` implementation under this constant instead of the
default. This is the escape
-hatch for every limitation described above: the framework's own mapping stays
deliberately conservative,
-but nothing stops an application from replacing it with one that fits its own
validators and locales.
+An application that wants a less conservative mapping — for example, emitting
`pattern` for
+case-insensitive regexes by rewriting them, or honouring `min`/`max` on a
plain text field — can register
+its own `HtmlConstraintProvider` implementation under this constant instead of
the default. This is the
+escape hatch for every limitation described above: the framework's own mapping
stays deliberately
+conservative, but nothing stops an application from replacing it with one that
fits its own validators and
+locales.
+
+Two things a provider cannot do. It cannot change an input's `type` — the
templates have already written
+it by the time the constraint map renders, so a `type` entry is discarded;
treating an `email` validator
+as `type="email"` needs a template override instead. And it cannot override an
attribute the developer set
+on the tag: a derived entry whose name matches a tag attribute or a dynamic
attribute (compared
+case-insensitively, as HTML does) is dropped, so the developer's own value
always wins.
## Pure JavaScript Client Side Validation (deprecated)
{#pure-javascript-client-side-validation-deprecated}