micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.06k stars 1.06k forks source link

POST of empty LocalDateTime value causes "Failed to convert argument" JSON response #11213

Open PeterFokkinga opened 1 week ago

PeterFokkinga commented 1 week ago

Expected Behavior

When posted form values are missing a value for what on the server is a LocalDateTime a NULL value should be used and the controller action should be called for the mapped url.

Actual Behaviour

The mapped controller action is never called but Micronaut will respond with HTTP 400 Bad Request and a JSON body (even when the request has an Accept header without JSON match, but that is not the main issue)

Steps To Reproduce

Given the following code:

@Controller
@Secured(SecurityRule.IS_ANONYMOUS)
public class TestController {
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    @Post("/test")
    public HttpResponse<?> test(@Body TestBean bean) {
        try {
            verify(bean);
        } catch (ConstraintViolationException e) {
            return HttpResponse.unprocessableEntity().body(e.getMessage()); // HTTP 422
        }
        return HttpResponse.accepted().body("ok"); // HTTP 202
    }

    void verify(@Valid TestBean bean) {}
}

@Serdeable
@Introspected
public record TestBean(
        @NotBlank String text,
        @NotNull LocalDateTime ts
        )
{ }

HTTP request with both values:

POST http://localhost:9002/test
Content-Type: application/x-www-form-urlencoded

text=test&ts=2024-09-28T124012

--> πŸ‘ HTTP 202 (Accepted)

HTTP request with missing text value

POST http://localhost:9002/test
Content-Type: application/x-www-form-urlencoded

text=&ts=2024-09-28T124012

--> πŸ‘ HTTP 422 (Unprocessable Entity) as expected the controller action is called and the verify method throws a ConstraintViolationException because of the missing value

HTTP request with missing date-time value

POST http://localhost:9002/test
Content-Type: application/x-www-form-urlencoded

text=test&ts=

--> πŸ‘Ž HTTP 400 (Bad Request) "Failed to convert argument [bean] for value [null] due to: Error deserializing type: TestBean bean" the controller action is never called and therefore cannot provide the response expected by the frontend

Environment Information

MacOS 14.4.1 Temurin SDK 21.0.2 Micronaut 4.6.1 (can't use 4.6.2 because of micronaut-projects/micronaut-security#1808 )

Example Application

No response

Version

4.6.1

altro3 commented 1 week ago

You marked field ts not null. That's why you have exception

PeterFokkinga commented 1 week ago

@altro3 no, even without the @NotNull annotation in the bean I get this behaviour; the @NotNull should cause a ConstraintViolationException when the verify method is called but it doesn't get that far.

dstepanov commented 1 week ago

Try combination of Micronaut Nullable and NotNull from the validation

PeterFokkinga commented 1 week ago

@dstepanov that does not work either. Which is good as the String property works fine without @Nullable

It appears that the problem is a request with an empty value: POST text=test&ts= does not work, but POST text=test works. It is also not specific to LocalDateTime, Integer and enum properties behave the same.

More experimentation and POST text= results in an empty string, not null. Which may explain the above behaviour (not knowing what "empty" LocalDateTime is).

I would argue though that form fields that are left blank should be interpreted as undefined ie NULL

sdelamo commented 1 week ago

I would argue though that form fields that are left blank should be interpreted as undefined ie NULL

I think we should do that.