Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConstraintViolationExceptions thrown from REST endpoints not firing custom ExceptionMapper #36276

Closed
tmulle opened this issue Oct 3, 2023 · 8 comments
Labels
area/hibernate-validator Hibernate Validator area/rest kind/bug Something isn't working

Comments

@tmulle
Copy link
Contributor

tmulle commented Oct 3, 2023

Describe the bug

Not sure if this is by design or not, but when I have the following signature on my rest endpoint where I want to validate the fields in the incoming bean, the validation never happens automatically.

I have to manually call validator.validate(formData) using the injected Validator in my class.

I didn't see anything in the docs saying I couldn't annotate a FormBean to use with the validation.
https://quarkus.io/guides/validation

The validation process doesn't happen automatically, uncommenting the validator code works and the validation happens.

Endpoint

@POST
    @Path("/ws/orgs/files/")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    public Response addOrgFile(@Valid OrgUploadData formData) {
        //Set<ConstraintViolation<OrgUploadData>> errors = validator.validate(formData);
        //if (!errors.isEmpty()) {
       //     throw new ConstraintViolationException(errors);
       // }
        ...rest of code
   }

Formbean

public class OrgUploadData {

    @RestForm("orgId")
    private Long orgId;

    @RestForm("fileTitle")
    @PartType(MediaType.TEXT_PLAIN)
    @Size(max=10, message = "Title must not be more than {max} characters")
    private String fileTitle;
}

Expected behavior

Validation to automatically happen

Actual behavior

Have to manually trigger using the validator injection class

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

3.4.1

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

No response

Additional information

No response

@tmulle tmulle added the kind/bug Something isn't working label Oct 3, 2023
@quarkus-bot
Copy link

quarkus-bot bot commented Oct 4, 2023

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

@geoand geoand added the area/hibernate-validator Hibernate Validator label Oct 4, 2023
@tmulle
Copy link
Contributor Author

tmulle commented Oct 4, 2023

So, I noticed that the @Valid annotation or even a manual throw of ConstraintViolation exception isn't firing my custom ExceptionMapper.

I'm putting together a global exception handler which formats the incoming exceptions to nicely formatted JSON output based on the type of exception being converted.

This works for other exceptions but it seems the ConstraintViolationException isn't being handled.

I tried both the JAX-RS way of using the @Provider and the @ServerExceptionMapper annotation as described in: https://quarkus.io/guides/resteasy-reactive#exception-mapping

I put together a demo at: https://github.com/tmulle/quarkus-validation-test

To test:

*** Normal Exception ***

  1. Call http://localhost:8080/validate/boom - Notice that the exception mapper is called and logs out to the console.

*** FORM encoded and Multipart Form ****

  1. POST a form value of name with the contents longer that 5 characters to http://localhost:8080/validate/multipart/auto with the Content-Type set to multipart/form-data - Notice the exception mapper is NOT trigger but you get a 400 response back showing the validation error.

Response:

{
	"details": "Error id cc56a09b-9721-4c22-8c4e-e70dcc2c785e-1, jakarta.validation.ConstraintViolationException: name: Name cannot be greater than 5 characters",
	"stack": "jakarta.validation.ConstraintViolationException: name: Name cannot be greater than 5 characters\n\tat org.acme.GreetingResource.handleMultiFormManual(GreetingResource.java:59)\n\tat org.acme.GreetingResource$quarkusrestinvoker$handleMultiFormManual_6f1a1832a12c22edbf8d2eb7eae7d9e20ed15c7f.invoke(Unknown Source)\n\tat org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)\n\tat io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)\n\tat org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)\n\tat io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)\n\tat org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)\n\tat org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)\n\tat org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)\n\tat org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)\n\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\n\tat java.base/java.lang.Thread.run(Thread.java:833)"
}
  1. POST the same data to http://localhost:8080/validate/multipart/auto and this time you get a different response back and still the exception mapper is not triggered.
ViolationReport{title='Constraint Violation', status=400, violations=[Violation{field='handleMultiFormAuto.request.name', message='Name cannot be greater than 5 characters'}]}
  1. POST the same date to http://localhost:8080/validate/form/auto and make sure the Content-Type of application/x-www-form-urlencoded and this is the response you get back and still no exception mapper trigger.
ViolationReport{title='Constraint Violation', status=400, violations=[Violation{field='handleFormEncodedAuto.request.name', message='Name cannot be greater than 5 characters'}]}
  1. POST the same data to http://localhost:8080/validate/form/manual and this is the response and no exception mapper trigger.
{
	"details": "Error id cc56a09b-9721-4c22-8c4e-e70dcc2c785e-2, jakarta.validation.ConstraintViolationException: name: Name cannot be greater than 5 characters",
	"stack": "jakarta.validation.ConstraintViolationException: name: Name cannot be greater than 5 characters\n\tat org.acme.GreetingResource.handleFormEncodedManual(GreetingResource.java:30)\n\tat org.acme.GreetingResource$quarkusrestinvoker$handleFormEncodedManual_2eb48224fc1a200a215282a28abf27c800482bb1.invoke(Unknown Source)\n\tat org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)\n\tat io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)\n\tat org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)\n\tat io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)\n\tat org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)\n\tat org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)\n\tat org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)\n\tat org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)\n\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\n\tat java.base/java.lang.Thread.run(Thread.java:833)"
}

This is with Quarkus 3.4.2 and 3.4.1

This is the resource:

@Path("/validate")
public class GreetingResource {

    @Inject
    Validator validator;

    @POST
    @Path("/form/manual")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    public Response handleFormEncodedManual(HelloRequest request) {
        Set<ConstraintViolation<HelloRequest>> errors = validator.validate(request);
        if (!errors.isEmpty()) {
            throw new ConstraintViolationException(errors);
        }

        return Response.ok("Hello " + request.getName()).build();
    }

    @POST
    @Path("/form/auto")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    public Response handleFormEncodedAuto(@Valid HelloRequest request) {
        return Response.ok("Hello " + request.getName()).build();
    }

    @POST
    @Path("/multipart/auto")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    public Response handleMultiFormAuto(@Valid HelloRequest request) {
        return Response.ok("Hello " + request.getName()).build();
    }

    @POST
    @Path("/multipart/manual")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    public Response handleMultiFormManual(HelloRequest request) {
        Set<ConstraintViolation<HelloRequest>> errors = validator.validate(request);
        if (!errors.isEmpty()) {
            throw new ConstraintViolationException(errors);
        }
        return Response.ok("Hello " + request.getName()).build();
    }
    
    @GET
    @Path("/boom")
    @Produces(MediaType.TEXT_PLAIN)
    public Response throwException() {
        throw new RuntimeException("Boom!");
    }
}

This is the bean trying to validate:

public class HelloRequest {
    
    @RestForm
    @Size(max=5, message = "Name cannot be greater than {max} characters")
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

@tmulle tmulle changed the title @Valid not firing for multipart form beans in REST endpoints @Valid not firing for multipart or URL encoded form beans in REST endpoints Oct 4, 2023
@tmulle tmulle changed the title @Valid not firing for multipart or URL encoded form beans in REST endpoints ConstraintViolationExceptions not firing custom ExceptionMapper Oct 4, 2023
@tmulle tmulle changed the title ConstraintViolationExceptions not firing custom ExceptionMapper ConstraintViolationExceptions thrown from REST endpoints not firing custom ExceptionMapper Oct 4, 2023
@tmulle
Copy link
Contributor Author

tmulle commented Oct 4, 2023

This original issue of @Valid not being called in endpoints seems like it might be a Quarkus Renarde issue since the demo I put together for this ticket uses straight Quarkus and no Renarde.

I will post a ticket over there because the @Valid isn't being called automatically when using Renarde

Sorry for all the changes, I went down the rabbit hole trying to find where the issue is and there seems to be 2 different issues.

@gsmet
Copy link
Member

gsmet commented Oct 6, 2023

I think ConstraintViolationException are handled by ResteasyReactiveViolationExceptionMapper in RESTEasy Reactive so could it just be a matter of priority?

But even when defining the priority, I wonder if the most precise exception mapper won't be selected anyway.

@FroMage and @geoand might know more about this behavior.

@geoand
Copy link
Contributor

geoand commented Oct 9, 2023

I think ConstraintViolationException are handled by ResteasyReactiveViolationExceptionMapper in RESTEasy Reactive

Correct

But even when defining the priority, I wonder if the most precise exception mapper won't be selected anyway

Correct again, that's what the JAX-RS / Jakarta REST spec mandates - more specific (in terms of the handled exception) exception mappers always take priority over more general ones

@geoand
Copy link
Contributor

geoand commented Oct 9, 2023

Sorry for all the changes, I went down the rabbit hole trying to find where the issue is and there seems to be 2 different issues.

Do you mind summorizing please?
Thanks

@melloware
Copy link
Contributor

Duplicate of #41102

@tmulle
Copy link
Contributor Author

tmulle commented Jun 18, 2024

closing since this is related to #41102

@tmulle tmulle closed this as completed Jun 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/hibernate-validator Hibernate Validator area/rest kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants