quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.66k stars 2.65k forks source link

Null Values allowed in @BeanParam? #31593

Closed tmulle closed 1 year ago

tmulle commented 1 year ago

Describe the bug

Sorry if this is a simple question, but I'm having trouble with empty fields on my HTML pages being sent to Quarkus using the @BeanParam.

My Bean has these fields:

@Data
@NoArgsConstructor
public class VersionFormData {

    @RestForm
    private Long id;

    @RestForm
    private Integer major;

    @RestForm
    private Integer minor;

    @RestForm
    private Integer build;

    @RestForm
    private Integer patch;

    @RestForm
    private String tag;

    @RestForm
    private String notes;

}

And my endpoint is defined as:

@Authenticated
    @POST
    @Path("/admin/version")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response saveVersion(@BeanParam VersionFormData data) {
        log.info("Saving version: " + data);
        return ok("OK");
    }

But when I send an empty form, ie. the Integer fields are not filled from the form, I get a 400 Bad Request.

Response
HTTP/1.1 400 Bad Request
Access-Control-Allow-Credentials: false
Content-Length: 0
Set-Cookie: _renarde_crsf=Nu05kXAPRzuixctYUlb6oA==; Path=/
Set-Cookie: _renarde_flash=e30=; Path=/
Access-Control-Allow-Origin: http://localhost:8080

Request Data
MIME Type: application/x-www-form-urlencoded
_renarde_crsf_token: Nu05kXAPRzuixctYUlb6oA==
major
minor
patch
build
tag
notes
id: 0

Is this supported? I would've thought that the empty/missing fields would have just been NULL in my POJO and not throw and error. It works fine for the STRING fields, just not any of the Int,Double, etc.

I even tried using @FormParam with @DefaultValue and still no luck:

@FormParam("id")
    private Long id;

    @FormParam("major")
    @DefaultValue(value = "0")
    private Integer major;

    @FormParam("minor")
    @DefaultValue(value = "0")
    private Integer minor;

    @FormParam("build")
    @DefaultValue(value = "0")
    private Integer build;

    @FormParam("patch")
    @DefaultValue(value = "0")
    private Integer patch;

Expected behavior

Form should still post and parse and the missing/empty fields should be NULL

Actual behavior

I got a 400 Bad Request

How to Reproduce?

No response

Output of uname -a or ver

No response

Output of java -version

No response

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.16.3.Final

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

No response

quarkus-bot[bot] commented 1 year ago

/cc @FroMage (resteasy-reactive), @Sgitario (resteasy-reactive), @geoand (resteasy-reactive), @stuartwdouglas (resteasy-reactive)

geoand commented 1 year ago

This is one for @FroMage

fwgreen commented 1 year ago

I'm having the same problem.

Quarkus version or git rev

3.0.0.Alpha6

TwoFX commented 1 year ago

I believe that this is working as intended.

The behaviour isn't specific to bean params, it's the same for normal @RestForm parameters. Also, RESTEasy Classic and RESTEasy Reactive behave in the same way, according to my tests.

Basically, there are three ways how you could try to leave a form parameter empty in the request:

  1. Just not put it in the request at all
  2. Put it in the request but without any value (e.g., firstParam&secondParam&thirdParam)
  3. Put it in the request but with an empty value (e.g., firstParam=&secondParam=&thirdParam=)

From the request log in the original issue it looks like the second approach.

The first approach will lead to the parameter being extracted as null.

The second and third approaches will be extracted as an empty string. This is mandated by the URL spec.

When extracting null, the final value of the parameter will be null. If the parameter is non-null, we run the converter, which for an Integer-valued parameter is Integer.valueOf. This will throw for an empty string, causing the 400 response.

fwgreen commented 1 year ago

@TwoFX Thanks for putting me on the right path: Changing my optional params to java.util.Optional resolved my issue.