== Avaje Validator

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

=== Usage

1) Add the dependency:

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

2) Configure annotation processor

.Maven
[source, xml, role = "primary", subs="verbatim,attributes"]
----
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>...</version>
      <configuration>
        <annotationProcessorPaths>
          <!-- if using lombok, it must be placed before the generator -->
          <path>
            <groupId>io.avaje</groupId>
            <artifactId>avaje-validator-generator</artifactId>
            <version>{avajeValidatorVersion}</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>
----

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

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

[NOTE]
====
Please note that the order of annotation processors is important. For example, if you're using `lombok`, the correct order should be: `lombok` -> `avaje-validator`
====

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(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

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

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

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

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

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

There is a handler version of it, so you can apply per route:

.validate
[source, java]
----
import io.jooby.validation.BeanValidator.validate;

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

=== Constraint Violations Rendering

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

.JSON:
[source, 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"
    }
  ]
}
----

[NOTE]
====
`AvajeValidatorModule` is compliant with `ProblemDetails`. Therefore, if you enable 
the Problem Details feature, the response above will be transformed into an `RFC 7807` compliant format
====

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);
  }));
}
----
