Lukasz Lenart created WW-5704:
---------------------------------

             Summary: HTML5 required false-rejects on radio/file when the bound 
property is never null
                 Key: WW-5704
                 URL: https://issues.apache.org/jira/browse/WW-5704
             Project: Struts 2
          Issue Type: Bug
            Reporter: Lukasz Lenart
             Fix For: 7.4.0


Found reviewing WW-5695 (PR #1865). Violates that feature's governing rule: 
emit a constraint only when the browser cannot reject input the server would 
accept.

h3. What happens

{{StrutsHtmlConstraintProvider.addRequiredField}} emits {{required}} for RADIO 
and FILE only, and its javadoc explains why:

bq. A control that submits an empty string rather than omitting the parameter 
therefore passes server-side while the browser blocks it [...] Only RADIO and 
FILE omit the parameter entirely when empty, so only they agree with the 
browser.

That reasoning is about the HTTP request. {{RequiredFieldValidator}} never sees 
the request - it inspects the already-bound property:

{code:java}
public void validate(Object object) throws ValidationException {
    String fieldName = getFieldName();
    Object value = this.getFieldValue(fieldName, object);

    if (value == null) {
        addFieldError(fieldName, object);
    } else if (value.getClass().isArray() && Array.getLength(value) == 0) {
        addFieldError(fieldName, object);
    } else if (Collection.class.isAssignableFrom(value.getClass()) && 
((Collection) value).isEmpty()) {
        addFieldError(fieldName, object);
    }
}
{code}

For a primitive-typed or otherwise default-initialised property the server can 
therefore _never_ fail, while the browser's {{required}} still can.

h3. Reproduction

The action declares {{private int priority;}} with a {{required}} 
field-validator on it, rendered as {{s:radio}} with {{name}} of {{priority}} 
and a list of 1, 2, 3. On first render nothing is selected, because 0 is not in 
the list.
* Browser: no radio in the group is selected, so the submit is blocked.
* Server: {{getFieldValue}} returns {{Integer.valueOf(0)}}, which is not null, 
so no error is added and the form would have been accepted.

Same shape for {{s:file}} whenever {{prepare()}} pre-populates the file 
property from an existing entity, which is the ordinary edit-an-attachment flow.

h3. Why it is not simply fixable in the provider

There is no render-time way to know a property's default value, so the two 
honest options are:
* drop the {{RequiredFieldValidator}} to {{required}} mapping entirely, leaving 
only {{requiredstring}}; or
* keep it and document it as a deliberate, narrow deviation from the 
never-false-reject rule.

h3. Not affected

The {{requiredstring}} mapping is sound and should stay: 
{{RequiredStringValidator}} fails on null, on empty, and by default on blank, 
so the browser's {{required}} can only reject what the server would also 
reject. That mapping reasons about the value, not the request, and so does not 
have this defect.

h3. Test gap

No test renders {{required}} end-to-end on {{s:radio}} or {{s:file}}; only 
{{minlength}} is asserted through a template.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to