Skip to content

Commit

Permalink
jooby-validation: skip double validation when filter is on lambda and…
Browse files Browse the repository at this point in the history
… controller

- jooby-apt: set the `io.jooby.validation.BeanValidator:true` route attribute
- fix #3530
  • Loading branch information
jknack committed Sep 15, 2024
1 parent 454c132 commit 13a0c7f
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 2 deletions.
4 changes: 4 additions & 0 deletions jooby/src/main/java/io/jooby/ForwardingContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,10 @@ public Context setUser(@Nullable Object user) {
return this;
}

public Context getDelegate() {
return ctx;
}

@NonNull @Override
public Object forward(@NonNull String path) {
Object result = ctx.forward(path);
Expand Down
5 changes: 5 additions & 0 deletions jooby/src/main/java/io/jooby/validation/BeanValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ static Route.Filter validate() {
static Route.Handler validate(Route.Handler next) {
return ctx -> {
try {
// The io.jooby.validation.BeanValidator attribute is set by jooby-apt. Skip validation
// let generated code to invoke BeanValidator.
if (ctx.getRoute().getAttributes().containsKey(BeanValidator.class.getName())) {
return next.apply(ctx);
}
return next.apply(new ValidationContext(ctx));
} catch (UndeclaredThrowableException | InvocationTargetException e) {
// unwrap reflective exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public List<String> generateMapping(boolean kt) {
var returnType = getReturnType();
var paramString = String.join(", ", getJavaMethodSignature(kt));
var javadocLink = javadocComment(kt);
var attributeGenerator = new RouteAttributesGenerator(context);
var attributeGenerator = new RouteAttributesGenerator(context, hasBeanValidation);
var routes = router.getRoutes();
var lastRoute = routes.get(routes.size() - 1).equals(this);
var entries = annotationMap.entrySet().stream().toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ private record EnumValue(String type, String value) {}

private final List<String> skip;
private final Types types;
private final boolean hasBeanValidation;

public RouteAttributesGenerator(MvcContext context) {
public RouteAttributesGenerator(MvcContext context, boolean hasBeanValidation) {
var environment = context.getProcessingEnvironment();
this.types = environment.getTypeUtils();
this.skip = Options.stringListOpt(environment, SKIP_ATTRIBUTE_ANNOTATIONS);
this.hasBeanValidation = hasBeanValidation;
}

public Optional<String> toSourceCode(boolean kt, MvcRoute route, int indent) {
Expand Down Expand Up @@ -137,6 +139,9 @@ private Map<String, Object> annotationMap(ExecutableElement method) {
var attributes = annotationMap(method.getEnclosingElement().getAnnotationMirrors());
// method
attributes.putAll(annotationMap(method.getAnnotationMirrors()));
if (hasBeanValidation) {
attributes.put("io.jooby.validation.BeanValidator", true);
}
return attributes;
}

Expand Down
20 changes: 20 additions & 0 deletions tests/src/test/java/io/jooby/i3530/Bean3530.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.i3530;

import jakarta.validation.constraints.NotEmpty;

public class Bean3530 {
@NotEmpty private String name;

public @NotEmpty String getName() {
return name;
}

public void setName(@NotEmpty String name) {
this.name = name;
}
}
17 changes: 17 additions & 0 deletions tests/src/test/java/io/jooby/i3530/Controller3530.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.i3530;

import io.jooby.annotation.POST;
import jakarta.validation.Valid;

public class Controller3530 {

@POST("/3530/controller")
public Bean3530 create(@Valid Bean3530 value) {
return value;
}
}
64 changes: 64 additions & 0 deletions tests/src/test/java/io/jooby/i3530/Issue3530.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.i3530;

import static org.junit.jupiter.api.Assertions.*;

import io.jooby.StatusCode;
import io.jooby.hibernate.validator.HibernateValidatorModule;
import io.jooby.jackson.JacksonModule;
import io.jooby.junit.ServerTest;
import io.jooby.junit.ServerTestRunner;
import io.jooby.validation.BeanValidator;
import io.jooby.validation.ValidationContext;
import okhttp3.MediaType;
import okhttp3.RequestBody;

public class Issue3530 {

@ServerTest
public void shouldRunValidationOnce(ServerTestRunner runner) {
runner
.define(
app -> {
app.install(new JacksonModule());
app.install(new HibernateValidatorModule());

app.use(BeanValidator.validate());

app.post(
"/3530/lambda",
ctx -> {
assertInstanceOf(ValidationContext.class, ctx);
return ctx.body(Bean3530.class);
});

// Must NOT be validation context:
app.use(
next ->
ctx -> {
assertFalse(ctx instanceof ValidationContext);
return next.apply(ctx);
});
app.mvc(new Controller3530_());
})
.ready(
http -> {
http.post(
"/3530/lambda",
RequestBody.create("{}", MediaType.parse("application/json")),
rsp -> {
assertEquals(StatusCode.UNPROCESSABLE_ENTITY.value(), rsp.code());
});
http.post(
"/3530/controller",
RequestBody.create("{}", MediaType.parse("application/json")),
rsp -> {
assertEquals(StatusCode.UNPROCESSABLE_ENTITY.value(), rsp.code());
});
});
}
}

0 comments on commit 13a0c7f

Please sign in to comment.