[
https://issues.apache.org/jira/browse/WW-5695?focusedWorklogId=1038323&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1038323
]
ASF GitHub Bot logged work on WW-5695:
--------------------------------------
Author: ASF GitHub Bot
Created on: 27/Aug/26 15:17
Start Date: 27/Aug/26 15:17
Worklog Time Spent: 10m
Work Description: lukaszlenart merged PR #1865:
URL: https://github.com/apache/struts/pull/1865
Issue Time Tracking
-------------------
Worklog Id: (was: 1038323)
Time Spent: 1h (was: 50m)
> Derive HTML5 constraint attributes from validators in the html5 theme
> ---------------------------------------------------------------------
>
> Key: WW-5695
> URL: https://issues.apache.org/jira/browse/WW-5695
> Project: Struts 2
> Issue Type: New Feature
> Reporter: Lukasz Lenart
> Priority: Major
> Fix For: 7.4.0
>
> Time Spent: 1h
> Remaining Estimate: 0h
>
> h2. Goal
> Let the {{html5}} theme emit native HTML5 constraint attributes
> ({{required}}, {{minlength}}, {{maxlength}}, {{pattern}}, {{min}}, {{max}})
> derived from the action's validators, as the successor to the JavaScript
> client-side validator deprecated in WW-5694.
> Because constraints ride on each individual input rather than on a generated
> per-form function, this also dissolves the root cause of WW-2975 instead of
> patching it — there is no central field list for a field to be missing from.
> h2. The governing rule: never false-reject
> A constraint is emitted only when the browser cannot reject something the
> server would accept. This rules out more than it first appears:
> {{min}} and {{max}} are inert on {{type="text"}} — they only apply to
> {{number}}, {{range}} and the temporal types. Honouring an int or double
> validator therefore means switching the input to {{type="number"}}, and that
> _is_ a false rejection: a browser {{type="number"}} refuses {{1234,50}},
> which the framework's locale-aware conversion accepts in a comma-decimal
> locale. The same argument rules out {{type="email"}} and {{type="url"}},
> whose browser regexes diverge from {{EmailValidator}} and {{URLValidator}} —
> see WW-4395, still open, for the email side of that divergence.
> Hence the rule: _Struts never sets or changes an input's type. It only adds
> constraints that are safe for whatever type is already there._ A developer
> who writes {{type="number"}} has accepted that widget's semantics, so {{min}}
> and {{max}} become pure additions.
> h2. Mapping
> || Validator || Emits || Condition ||
> | required | required | always |
> | requiredstring | required | always; the server is stricter on
> whitespace-only input, which is safe |
> | stringlength | minlength / maxlength | only when {{trim="false"}} — the
> server measures the trimmed value, so a maxlength derived from a trimming
> validator would stop the user typing input the server would accept |
> | regex | pattern | only when {{caseSensitive="true"}} and the regex is
> ECMAScript-safe |
> | int, short, long | min / max | only when the control is already numeric |
> | double | min / max | only when the control is already numeric |
> | date | min / max | only when the control is already temporal |
> | email, url | nothing | browser regexes diverge from the framework's |
> | creditcard, fieldexpression, expression, conversion, visitor | nothing | no
> safe mapping |
> {{RegexFieldValidator}} uses {{matcher.matches()}}, so it is fully anchored
> and matches HTML5 {{pattern}} semantics. The divergence is syntactic, not
> positional — Java-only constructs such as POSIX classes, possessive
> quantifiers and {{\\A}} / {{\\z}} are the hazard, and
> {{caseSensitive="false"}} has no {{pattern}} equivalent because HTML allows
> no regex flags.
> ECMAScript-safety detection must be an _allowlist_, not a denylist: a
> denylist violates the rule the first time it misses a construct. Allow
> literals, {{\\d}} {{\\w}} {{\\s}} and their negations, character classes
> without POSIX or Unicode property syntax, grouping, alternation, anchors and
> bounded quantifiers; emit no {{pattern}} for anything else. This is
> deliberately strict and is the piece most likely to need tuning after real
> use.
> h2. Design
> * New constant {{struts.ui.html5.constraints}}, default {{false}} in 7.4.0.
> The {{html5}} theme shipped in 7.2.0, so emitting {{required}} on upgrade
> would start blocking submits on forms that render unchanged today. The
> default flips to {{true}} in 8.0.0.
> * New enum {{HtmlControlType}} — the provider's real question is which
> constraint attributes are legal on a control, not what string is in the type
> attribute. It models the control rather than the attribute, because textarea
> and select have no type attribute yet do accept {{required}}. Members cover
> the HTML5 input types plus TEXTAREA, SELECT and OTHER, with predicates
> {{supportsPattern()}}, {{supportsLength()}} and {{supportsRange()}}. Its
> {{from(String)}} factory must never throw: the type attribute is
> OGNL-evaluated, so at runtime it can be any string, and unknown values
> normalise to OTHER, which supports nothing — the conservative default falls
> out for free.
> * New interface {{HtmlConstraintProvider}} with default implementation
> {{StrutsHtmlConstraintProvider}}, taking the field's validators and an
> {{HtmlControlType}} and returning a map of attribute name to value.
> Registered once in {{struts-beans.xml}} following the {{UrlRenderer}} model —
> registering a bean under two types builds two instances. Because the default
> policy is deliberately restrictive, the swappable bean is how applications
> wanting best-effort mapping get served.
> * {{UIBean.evaluateParams}} already resolves the {{Form}} ancestor to
> populate {{tagNames}}; that is the hook. Gate the computation on the constant
> so the cost is zero when off.
> * New {{Form.getFieldValidators(String)}} that resolves the action's
> validator list once and memoises it on the form's attributes, then filters
> per call. The existing {{getValidators(String)}} re-runs the action-mapping
> lookup on every call, so a twenty-field form would do twenty full lookups.
> * New {{html5/constraints.ftl}}, included from {{common-attributes.ftl}} so
> every html5 input picks it up without per-template edits.
> h2. Messages
> The provider returns the full attribute set, not only constraints. Validator
> messages ride the same map as {{data-msg-}} entries keyed by validator type —
> {{data-msg-required}}, {{data-msg-stringlength}} — resolved via
> {{validator.getMessage(action)}}, which goes through
> {{DelegatingValidatorContext}} and {{textProviderFactory}} and is therefore
> properly i18n'd.
> A message is emitted for every validator that has one, _including_ those
> producing no constraint. An email validator therefore contributes
> {{data-msg-email}} and nothing else, which is exactly where an application
> most needs it. Struts ships nothing that consumes these attributes — no
> JavaScript.
> h2. Do not conflate requiredLabel
> {{requiredLabel}} keeps meaning "draw an asterisk next to the label". It must
> never produce a {{required}} attribute — only a {{required}} validator does.
> This is the most likely regression in this work and needs an explicit test.
> h2. Testing
> The negative cases carry the weight, because they are what protects the rule:
> {{stringlength trim="true"}} emits no length constraints; {{regex
> caseSensitive="false"}} emits no pattern; a Java-only regex emits no pattern;
> int and double on a text control emit no range; email and url never set or
> change the type; {{HtmlControlType.from}} never throws on null or unknown
> input.
> Note the harness trap: a form-validation tag test needs {{initDispatcher}}
> with {{TestConfigurationProvider}} _and_ {{createMocks()}} in setUp, plus a
> {{prepareMockInvocation()}} EasyMock helper. Without them
> {{evaluateClientSideJsEnablement}} finds no {{ValidationInterceptor}},
> {{performValidation}} stays false, and no validation function is emitted at
> all — so the test passes or fails for entirely the wrong reason.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)