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

Add Avaje Validator Module #3519

Merged
merged 4 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion docs/asciidoc/modules/avaje-inject.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
<version>10.0</version>
<version>10.3</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down
311 changes: 311 additions & 0 deletions docs/asciidoc/modules/avaje-validator.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
== Avaje Validator
SentryMan marked this conversation as resolved.
Show resolved Hide resolved

Bean validation via https://avaje.io/validator/[Avaje Validator].

=== Usage

1) Add the dependency:

[dependency, artifactId="jooby-avaje-validator"]
.

SentryMan marked this conversation as resolved.
Show resolved Hide resolved
2) Configure annotation processor

.Maven
[source, xml, role = "primary"]
----
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>...</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-validator-generator</artifactId>
<version>2.1</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
----

.Gradle
[source, kotlin, role = "secondary"]
----
plugins {
id "org.jetbrains.kotlin.kapt" version "1.9.10"
}

dependencies {
kapt 'io.avaje:avaje-validator-generator:2.1'
}
----

3) Install

.Java
[source, java, role="primary"]
----
import io.jooby.avaje.validator.AvajeValidatorModule;

{
install(new AvajeValidatorModule());
}
----

.Kotlin
[source, kt, role="secondary"]
----
import io.jooby.avaje.validator.AvajeValidatorModule

{
install(new AvajeValidatorModule())
}
----

4) Usage in MVC routes

.Java
[source,java,role="primary"]
----
import io.jooby.annotation.*;
import jakarta.validation.Valid;

@Path("/mvc")
public class Controller {

@POST("/validate-body")
public void validateBody(@Valid Bean bean) { // <1>
...
}

@POST("/validate-query")
public void validateQuery(@Valid @QueryParam Bean bean) { // <2>
...
}

@POST("/validate-list")
public void validateList(@Valid List<Bean> beans) { // <3>
...
}

@POST("/validate-map")
public void validateMap(@Valid Map<String, Bean> beans) { // <4>
...
}
}
----

.Kotlin
[source, kt, role="secondary"]
----
import io.jooby.annotation.*;
import jakarta.validation.Valid

@Path("/mvc")
class Controller {

@POST("/validate-body")
fun validateBody(@Valid bean: Bean) : Unit { // <1>
...
}

@POST("/validate-query")
fun validateQuery(@Valid @QueryParam bean: Bean) : Unit { // <2>
...
}

@POST("/validate-list")
fun validateList(@Valid beans: List<Bean>) : Unit { // <3>
...
}

@POST("/validate-map")
fun validateMap(@Valid beans: Map<String, Bean>) : Unit { // <4>
...
}
}
----

<1> Validate a bean decoded from the request body
<2> Validate a bean parsed from query parameters. This works the same for `@FormParam` or `@BindParam`
<3> Validate a list of beans. This also applies to arrays `@Valid Bean[] beans`
<4> Validate a map of beans

4) Usage in in script/lambda routes

Jooby doesn't provide fully native bean validation in script/lambda at the moment,
but you can use a helper that we utilize under the hood in MVC routes:

.Java
[source, java, role="primary"]
----
import io.jooby.validation.BeanValidator;

{
post("/validate", ctx -> {
Bean bean = BeanValidator.validate(ctx, ctx.body(Bean.class));
...
});
}
----

.Kotlin
[source, kt, role="secondary"]
----
import io.jooby.validation.BeanValidator

{
post("/validate") {
val bean = BeanValidator.validate(ctx, ctx.body(Bean.class))
...
}
}
----

`BeanValidator.validate()` behaves identically to validation in MVC routes.
It also supports validating list, array, and map of beans

=== Constraint Violations Rendering

`AvajeValidatorModule` provides default built-in error handler that
catches `ConstraintViolationException` and transforms it into the following response:

.JSON:
----
{
"title": "Validation failed",
"status": 422,
"errors": [
{
"field": "firstName",
"messages": [
"must not be empty",
"must not be null"
],
"type": "FIELD"
},
{
"field": null,
"messages": [
"passwords are not the same"
],
"type": "GLOBAL"
}
]
}
----

It is possible to override the `title` and `status` code of the response above:

[source, java]
----

{
install(new AvajeJsonbModule());
install(new AvajeValidatorModule()
.statusCode(StatusCode.BAD_REQUEST)
.validationTitle("Incorrect input data")
);
}
----

If the default error handler doesn't fully meet your needs, you can always disable it and provide your own:

[source, java]
----

{
install(new AvajeJsonbModule());
install(new AvajeValidatorModule().disableViolationHandler());

error(ConstraintViolationException.class, new MyConstraintViolationHandler());
}
----

=== Manual Validation

The module exposes `Validator` as a service, allowing you to run validation manually at any time.

==== Script/lambda:

[source, java]
----
import io.avaje.validation.Validator;

{
post("/validate", ctx -> {
Validator validator = require(Validator.class);
validator.validate(ctx.body(Bean.class));
...
});
}
----

==== MVC routes with dependency injection:

1) Install DI framework at first.

[source, java]
----
import io.jooby.avaje.validator.AvajeValidatorModule;

{
install(AvajeInjectModule.of()); // <1>
install(new AvajeValidatorModule());
}
----

<1> `Avaje` is just an example, you can achieve the same with `Dagger` or `Guice`

2) Inject `Validator` in controller, service etc.

[source, java]
----
import io.avaje.validation.Validator;
import jakarta.inject.Inject;

@Path("/mvc")
public class Controller {

private final Validator validator;

@Inject
public Controller(Validator validator) {
this.validator = validator;
}

@POST("/validate")
public void validate(Bean bean) {
Set<ConstraintViolation<Bean>> violations = validator.validate(bean);
...
}
}
----

=== Configuration
Any property defined at `validation` will be added automatically:

.application.conf
[source, properties]
----
validation.fail_fast = true
----

Or programmatically:

[source, java]
----
import io.jooby.avaje.validator.AvajeValidatorModule;

{
install(new AvajeValidatorModule().doWith(cfg -> {
cfg.failFast(true);
}));
}
----
1 change: 1 addition & 0 deletions docs/asciidoc/modules/modules.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Available modules are listed next.
* link:/modules/redis[Redis]: Redis module.

=== Validation
* link:/modules/avaje-validator[Avaje Validator]: Avaje Validator module.
* link:/modules/hibernate-validator[Hibernate Validator]: Hibernate Validator module.

=== Development Tools
Expand Down
35 changes: 6 additions & 29 deletions modules/jooby-avaje-inject/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@

<modelVersion>4.0.0</modelVersion>
<artifactId>jooby-avaje-inject</artifactId>


<properties>
<maven.compiler.proc>full</maven.compiler.proc>
SentryMan marked this conversation as resolved.
Show resolved Hide resolved
</properties>

<dependencies>
<dependency>
<groupId>com.github.spotbugs</groupId>
Expand All @@ -21,7 +25,6 @@
<dependency>
<groupId>io.jooby</groupId>
<artifactId>jooby</artifactId>
<version>${jooby.version}</version>
</dependency>

<!-- Avaje Inject -->
Expand All @@ -33,7 +36,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
<scope>provided</scope>
<scope>test</scope>
</dependency>

<!-- Test dependencies -->
Expand Down Expand Up @@ -74,30 +77,4 @@
<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.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Loading