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

Feat/jooby hibernate validator #3508

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions modules/jooby-apt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-validation</artifactId>
<version>${jooby.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<scope>test</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>com.google.testing.compile</groupId>
Expand Down
18 changes: 18 additions & 0 deletions modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
return false;
} else {
var routeMap = buildRouteRegistry(annotations, roundEnv);
verifyBeanValidationDependency(routeMap.values());
for (var router : routeMap.values()) {
try {
var sourceCode = router.toSourceCode(null);
Expand Down Expand Up @@ -371,4 +372,21 @@ public static RuntimeException propagate(final Throwable x) {
private static <E extends Throwable> E sneakyThrow0(final Throwable x) throws E {
throw (E) x;
}

private void verifyBeanValidationDependency(Collection<MvcRouter> routers) {
var hasBeanValidation = routers.stream().anyMatch(MvcRouter::hasBeanValidation);
if (hasBeanValidation) {
TypeElement validatorElement = processingEnv.getElementUtils()
.getTypeElement("io.jooby.validation.BeanValidator");

if (validatorElement == null) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"Unable to load 'BeanValidator' class. " +
"Bean validation usage (@Valid) was detected, but the appropriate dependency is missing. " +
"Please ensure that you have added the corresponding validation dependency " +
"(e.g., jooby-hbv).");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: jooby-hibernate-validator

}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ public class MvcParameter {
private final VariableElement parameter;
private final Map<String, AnnotationMirror> annotations;
private final TypeDefinition type;
private final boolean requireBeanValidation;

public MvcParameter(MvcContext context, MvcRoute route, VariableElement parameter) {
this.route = route;
this.parameter = parameter;
this.annotations = annotationMap(parameter);
this.type =
new TypeDefinition(context.getProcessingEnvironment().getTypeUtils(), parameter.asType());
this.requireBeanValidation = annotations.get("jakarta.validation.Valid") != null;
}

public TypeDefinition getType() {
Expand Down Expand Up @@ -144,4 +146,8 @@ private Map<String, AnnotationMirror> annotationMap(VariableElement parameter) {
private List<? extends AnnotationMirror> annotationFromAnnotationType(Element element) {
return Optional.ofNullable(element.getAnnotationMirrors()).orElse(Collections.emptyList());
}

public boolean isRequireBeanValidation() {
return requireBeanValidation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ public class MvcRoute {
private String generatedName;
private final boolean suspendFun;
private boolean uncheckedCast;
private final boolean hasBeanValidation;

public MvcRoute(MvcContext context, MvcRouter router, ExecutableElement method) {
this.context = context;
this.router = router;
this.method = method;
this.parameters =
method.getParameters().stream().map(it -> new MvcParameter(context, this, it)).toList();
this.hasBeanValidation = parameters.stream().anyMatch(MvcParameter::isRequireBeanValidation);
this.suspendFun =
!parameters.isEmpty()
&& parameters.get(parameters.size() - 1).getType().is("kotlin.coroutines.Continuation");
Expand All @@ -51,6 +53,7 @@ public MvcRoute(MvcContext context, MvcRouter router, MvcRoute route) {
this.method = route.method;
this.parameters =
method.getParameters().stream().map(it -> new MvcParameter(context, this, it)).toList();
this.hasBeanValidation = parameters.stream().anyMatch(MvcParameter::isRequireBeanValidation);
this.returnType =
new TypeDefinition(
context.getProcessingEnvironment().getTypeUtils(), method.getReturnType());
Expand Down Expand Up @@ -203,7 +206,16 @@ public List<String> generateHandlerCall(boolean kt) {
/* Parameters */
var paramList = new StringJoiner(", ", "(", ")");
for (var parameter : getParameters(true)) {
paramList.add(parameter.generateMapping(kt));
String generatedParameter = parameter.generateMapping(kt);
if (parameter.isRequireBeanValidation()) {
generatedParameter = CodeBlock.of(
"io.jooby.validation.BeanValidator.validate(",
"ctx, ",
generatedParameter,
")");
}

paramList.add(generatedParameter);
}
var throwsException = !method.getThrownTypes().isEmpty();
var returnTypeGenerics =
Expand Down Expand Up @@ -482,4 +494,8 @@ private String javadocComment(boolean kt) {
public void setUncheckedCast(boolean value) {
this.uncheckedCast = value;
}

public boolean hasBeanValidation() {
return hasBeanValidation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,8 @@ public String toString() {
buffer.append("}");
return buffer.toString();
}

public boolean hasBeanValidation() {
return getRoutes().stream().anyMatch(MvcRoute::hasBeanValidation);
}
}
19 changes: 19 additions & 0 deletions modules/jooby-apt/src/test/java/tests/validation/Bean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tests.validation;

import io.jooby.Context;

class Bean {
private String name;

public String getName() {
return name;
}

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

public static Bean map(Context ctx) {
return ctx.body(Bean.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package tests.validation;

import io.jooby.apt.ProcessorRunner;
import org.junit.jupiter.api.Test;

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

public class BeanValidationGeneratorTest {

@Test
public void generate_validation_forBean() throws Exception {
new ProcessorRunner(new BeanValidationsController()).withSource(
false,
source -> {
assertTrue(source.contains(
"c.validateQueryBean(io.jooby.validation.BeanValidator.validate(ctx, ctx.query(\"bean\").isMissing() ? ctx.query().toNullable(tests.validation.Bean.class) : ctx.query(\"bean\").toNullable(tests.validation.Bean.class)))")
);

assertTrue(source.contains(
"c.validateFormBean(io.jooby.validation.BeanValidator.validate(ctx, ctx.form(\"bean\").isMissing() ? ctx.form().toNullable(tests.validation.Bean.class) : ctx.form(\"bean\").toNullable(tests.validation.Bean.class)))")
);

assertTrue(source.contains(
"c.validateBindParamBean(io.jooby.validation.BeanValidator.validate(ctx, tests.validation.Bean.map(ctx)))")
);

assertTrue(source.contains(
"c.validateBodyBean(io.jooby.validation.BeanValidator.validate(ctx, ctx.body(tests.validation.Bean.class)))")
);

assertTrue(source.contains(
"c.validateListOfBodyBeans(io.jooby.validation.BeanValidator.validate(ctx, ctx.body(io.jooby.Reified.list(tests.validation.Bean.class).getType())))")
);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tests.validation;

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

import java.util.List;

public class BeanValidationsController {

@POST("/validate/query-bean")
public Bean validateQueryBean(@Valid @QueryParam Bean bean) {
return bean;
}

@POST("/validate/form-bean")
public Bean validateFormBean(@Valid @FormParam Bean bean) {
return bean;
}

//todo: revive when flash `toNullable` will be fixed
// @POST("/validate/flash-bean")
// public Bean validateFlashBean(@Valid @FlashParam Bean bean) {
// return bean;
// }

@POST("/validate/bind-param-bean")
public Bean validateBindParamBean(@Valid @BindParam Bean bean) {
return bean;
}

@POST("/validate/body-bean")
public Bean validateBodyBean(@Valid Bean bean) {
return bean;
}

@POST("/validate/list-of-body-beans")
public List<Bean> validateListOfBodyBeans(@Valid List<Bean> bean) {
return bean;
}
}
119 changes: 119 additions & 0 deletions modules/jooby-hibernate-validator/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<parent>
<groupId>io.jooby</groupId>
<artifactId>modules</artifactId>
<version>3.2.10-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>jooby-hibernate-validator</artifactId>

<dependencies>
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby</artifactId>
<version>${jooby.version}</version>
</dependency>

<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-validation</artifactId>
<version>${jooby.version}</version>
</dependency>

<!-- Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>

<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
<version>5.0.1</version>
</dependency>

<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>5.0.0</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-netty</artifactId>
<version>${jooby.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-jackson</artifactId>
<version>${jooby.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby-test</artifactId>
<version>${jooby.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>test</id>
<phase>test-compile</phase>
</execution>
</executions>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>io.jooby</groupId>
<artifactId>jooby-apt</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Loading