diff --git a/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/AvajeValidatorModule.java b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/AvajeValidatorModule.java index bdc50beae5..ea77e3df35 100644 --- a/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/AvajeValidatorModule.java +++ b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/AvajeValidatorModule.java @@ -7,12 +7,12 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; -import java.util.Optional; +import java.util.*; import java.util.function.Consumer; +import java.util.function.Function; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigValueType; import edu.umd.cs.findbugs.annotations.NonNull; import io.avaje.validation.ConstraintViolationException; import io.avaje.validation.Validator; @@ -46,11 +46,11 @@ * ConstraintViolationException} and transforms it into a {@link * io.jooby.validation.ValidationResult} * - * @authors kliushnichenko, SentryMan + * @author kliushnichenko + * @author SentryMan * @since 3.3.1 */ public class AvajeValidatorModule implements Extension { - private Consumer configurer; private StatusCode statusCode = StatusCode.UNPROCESSABLE_ENTITY; private String title = "Validation failed"; @@ -105,43 +105,34 @@ public AvajeValidatorModule disableViolationHandler() { } @Override - public void install(@NonNull Jooby app) throws Exception { - - var props = app.getEnvironment(); + public void install(@NonNull Jooby app) { + var conf = app.getConfig(); - final var locales = new ArrayList(); final var builder = Validator.builder(); - Optional.ofNullable(props.getProperty("validation.failFast", "false")) - .map(Boolean::valueOf) - .ifPresent(builder::failFast); + withProperty(conf, "validation.failFast", conf::getBoolean).ifPresent(builder::failFast); - Optional.ofNullable(props.getProperty("validation.resourcebundle.names")) - .map(s -> s.split(",")) - .ifPresent(builder::addResourceBundles); - - Optional.ofNullable(props.getProperty("validation.locale.default")) - .map(Locale::forLanguageTag) + withProperty(conf, "validation.resourcebundle.names", path -> getStringList(conf, path)) + .ifPresent(values -> values.forEach(builder::addResourceBundles)); + // Locales from application + Optional.ofNullable(app.getLocales()) .ifPresent( - l -> { - builder.setDefaultLocale(l); - locales.add(l); + locales -> { + builder.setDefaultLocale(locales.get(0)); + locales.stream().skip(1).forEach(builder::addLocales); }); - - Optional.ofNullable(props.getProperty("validation.locale.addedLocales")).stream() - .flatMap(s -> Arrays.stream(s.split(","))) + withProperty(conf, "validation.locale.default", conf::getString) .map(Locale::forLanguageTag) - .forEach( - l -> { - builder.addLocales(l); - locales.add(l); - }); - - Optional.ofNullable(props.getProperty("validation.temporal.tolerance.value")) - .map(Long::valueOf) + .ifPresent(builder::setDefaultLocale); + withProperty(conf, "validation.locale.addedLocales", path -> getStringList(conf, path)) + .orElseGet(List::of) + .stream() + .map(Locale::forLanguageTag) + .forEach(builder::addLocales); + withProperty(conf, "validation.temporal.tolerance.value", conf::getLong) .ifPresent( duration -> { - final var unit = - Optional.ofNullable(props.getProperty("validation.temporal.tolerance.chronoUnit")) + var unit = + withProperty(conf, "validation.temporal.tolerance.chronoUnit", conf::getString) .map(ChronoUnit::valueOf) .orElse(ChronoUnit.MILLIS); builder.temporalTolerance(Duration.of(duration, unit)); @@ -151,13 +142,12 @@ public void install(@NonNull Jooby app) throws Exception { configurer.accept(builder); } - Validator validator = builder.build(); + var validator = builder.build(); app.getServices().put(Validator.class, validator); app.getServices().put(MvcValidator.class, new MvcValidatorImpl(validator)); if (!disableDefaultViolationHandler) { - app.error( - ConstraintViolationException.class, new ConstraintViolationHandler(statusCode, title)); + app.error(new ConstraintViolationHandler(statusCode, title)); } } @@ -174,4 +164,17 @@ public void validate(Context ctx, Object bean) throws ConstraintViolationExcepti validator.validate(bean, ctx.locale()); } } + + private static Optional withProperty(Config config, String path, Function fn) { + if (config.hasPath(path)) { + return Optional.of(fn.apply(path)); + } + return Optional.empty(); + } + + private static List getStringList(Config config, String path) { + return config.getValue(path).valueType() == ConfigValueType.STRING + ? List.of(config.getString(path)) + : config.getStringList(path); + } } diff --git a/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/ConstraintViolationHandler.java b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/ConstraintViolationHandler.java index 76181c7343..172a3ffd02 100644 --- a/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/ConstraintViolationHandler.java +++ b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/ConstraintViolationHandler.java @@ -1,3 +1,8 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ package io.jooby.avaje.validator; import static io.jooby.validation.ValidationResult.ErrorType.FIELD; @@ -49,36 +54,32 @@ * @since 3.2.10 */ public class ConstraintViolationHandler implements ErrorHandler { - private static final String ROOT_VIOLATIONS_PATH = ""; - private final StatusCode statusCode; private final String title; - public ConstraintViolationHandler(StatusCode statusCode, String title) { + public ConstraintViolationHandler(@NonNull StatusCode statusCode, @NonNull String title) { this.statusCode = statusCode; this.title = title; } @Override public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) { - var ex = (ConstraintViolationException) cause; + if (cause instanceof ConstraintViolationException ex) { + var violations = ex.violations(); - var violations = ex.violations(); + var groupedByPath = violations.stream().collect(groupingBy(ConstraintViolation::path)); + var errors = collectErrors(groupedByPath); - Map> groupedByPath = - violations.stream().collect(groupingBy(violation -> violation.path().toString())); - - List errors = collectErrors(groupedByPath); - - ValidationResult result = new ValidationResult(title, statusCode.value(), errors); - ctx.setResponseCode(statusCode).render(result); + var result = new ValidationResult(title, statusCode.value(), errors); + ctx.setResponseCode(statusCode).render(result); + } } private List collectErrors( Map> groupedViolations) { List errors = new ArrayList<>(); - for (Map.Entry> entry : groupedViolations.entrySet()) { + for (var entry : groupedViolations.entrySet()) { var path = entry.getKey(); if (ROOT_VIOLATIONS_PATH.equals(path)) { errors.add(new ValidationResult.Error(null, extractMessages(entry.getValue()), GLOBAL)); diff --git a/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/package-info.java b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/package-info.java index aaf68dc3bb..9eea00ea0c 100644 --- a/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/package-info.java +++ b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/package-info.java @@ -1 +1,25 @@ +/** + * Avaje Validator Module: https://jooby.io/modules/avaje-validator. + * + *
{@code
+ * {
+ *   install(new AvajeValidatorModule());
+ *
+ * }
+ *
+ * public class Controller {
+ *
+ *   @POST("/create")
+ *   public void create(@Valid Bean bean) {
+ *   }
+ *
+ * }
+ * }
+ * + *

Supports validation of a single bean, list, array, or map. + * + *

The module also provides a built-in error handler that catches {@link + * io.avaje.validation.ConstraintViolationException} and transforms it into a {@link + * io.jooby.validation.ValidationResult} + */ package io.jooby.avaje.validator; diff --git a/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/ConstraintViolationHandler.java b/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/ConstraintViolationHandler.java index dcc5f16bbc..376b095e4d 100644 --- a/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/ConstraintViolationHandler.java +++ b/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/ConstraintViolationHandler.java @@ -1,5 +1,18 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ package io.jooby.hibernate.validator; +import static io.jooby.validation.ValidationResult.ErrorType.FIELD; +import static io.jooby.validation.ValidationResult.ErrorType.GLOBAL; +import static java.util.stream.Collectors.groupingBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import edu.umd.cs.findbugs.annotations.NonNull; import io.jooby.Context; import io.jooby.ErrorHandler; @@ -8,19 +21,11 @@ import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static io.jooby.validation.ValidationResult.ErrorType.FIELD; -import static io.jooby.validation.ValidationResult.ErrorType.GLOBAL; -import static java.util.stream.Collectors.groupingBy; - /** * Catches and transform {@link ConstraintViolationException} into {@link ValidationResult} - *

- * Payload example: + * + *

Payload example: + * *

{@code
  * {
  *    "title": "Validation failed",
@@ -50,45 +55,47 @@
  */
 public class ConstraintViolationHandler implements ErrorHandler {
 
-    private static final String ROOT_VIOLATIONS_PATH = "";
+  private static final String ROOT_VIOLATIONS_PATH = "";
 
-    private final StatusCode statusCode;
-    private final String title;
+  private final StatusCode statusCode;
+  private final String title;
 
-    public ConstraintViolationHandler(StatusCode statusCode, String title) {
-        this.statusCode = statusCode;
-        this.title = title;
-    }
+  public ConstraintViolationHandler(@NonNull StatusCode statusCode, @NonNull String title) {
+    this.statusCode = statusCode;
+    this.title = title;
+  }
 
-    @Override
-    public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) {
-        ConstraintViolationException ex = (ConstraintViolationException) cause;
+  @Override
+  public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) {
+    if (cause instanceof ConstraintViolationException ex) {
+      var violations = ex.getConstraintViolations();
 
-        Set> violations = ex.getConstraintViolations();
+      var groupedByPath =
+          violations.stream()
+              .collect(groupingBy(violation -> violation.getPropertyPath().toString()));
 
-        Map>> groupedByPath = violations.stream()
-                .collect(groupingBy(violation -> violation.getPropertyPath().toString()));
+      var errors = collectErrors(groupedByPath);
 
-        List errors = collectErrors(groupedByPath);
-
-        ValidationResult result = new ValidationResult(title, statusCode.value(), errors);
-        ctx.setResponseCode(statusCode).render(result);
+      var result = new ValidationResult(title, statusCode.value(), errors);
+      ctx.setResponseCode(statusCode).render(result);
     }
+  }
 
-    private List collectErrors(Map>> groupedViolations) {
-        List errors = new ArrayList<>();
-        for (Map.Entry>> entry : groupedViolations.entrySet()) {
-            var path = entry.getKey();
-            if (ROOT_VIOLATIONS_PATH.equals(path)) {
-                errors.add(new ValidationResult.Error(null, extractMessages(entry.getValue()), GLOBAL));
-            } else {
-                errors.add(new ValidationResult.Error(path, extractMessages(entry.getValue()), FIELD));
-            }
-        }
-        return errors;
+  private List collectErrors(
+      Map>> groupedViolations) {
+    List errors = new ArrayList<>();
+    for (var entry : groupedViolations.entrySet()) {
+      var path = entry.getKey();
+      if (ROOT_VIOLATIONS_PATH.equals(path)) {
+        errors.add(new ValidationResult.Error(null, extractMessages(entry.getValue()), GLOBAL));
+      } else {
+        errors.add(new ValidationResult.Error(path, extractMessages(entry.getValue()), FIELD));
+      }
     }
+    return errors;
+  }
 
-    private List extractMessages(List> violations) {
-        return violations.stream().map(ConstraintViolation::getMessage).toList();
-    }
+  private List extractMessages(List> violations) {
+    return violations.stream().map(ConstraintViolation::getMessage).toList();
+  }
 }
diff --git a/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/HibernateValidatorModule.java b/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/HibernateValidatorModule.java
index 5f7a3afe95..010fabc773 100644
--- a/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/HibernateValidatorModule.java
+++ b/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/HibernateValidatorModule.java
@@ -5,24 +5,21 @@
  */
 package io.jooby.hibernate.validator;
 
-import com.typesafe.config.Config;
+import static jakarta.validation.Validation.byProvider;
+
+import java.util.function.Consumer;
+
+import org.hibernate.validator.HibernateValidator;
+import org.hibernate.validator.HibernateValidatorConfiguration;
+
 import edu.umd.cs.findbugs.annotations.NonNull;
 import io.jooby.Context;
 import io.jooby.Extension;
 import io.jooby.Jooby;
 import io.jooby.StatusCode;
 import io.jooby.validation.MvcValidator;
-import jakarta.validation.ConstraintViolation;
 import jakarta.validation.ConstraintViolationException;
 import jakarta.validation.Validator;
-import jakarta.validation.ValidatorFactory;
-import org.hibernate.validator.HibernateValidator;
-import org.hibernate.validator.HibernateValidatorConfiguration;
-
-import java.util.Set;
-import java.util.function.Consumer;
-
-import static jakarta.validation.Validation.byProvider;
 
 /**
  * Hibernate Validator Module: https://jooby.io/modules/hibernate-validator.
@@ -42,112 +39,115 @@
  * }
  * }
* - *

Supports validation of a single bean, list, array, or map.

+ *

Supports validation of a single bean, list, array, or map. * - *

The module also provides a built-in error handler that catches {@link ConstraintViolationException} - * and transforms it into a {@link io.jooby.validation.ValidationResult}

+ *

The module also provides a built-in error handler that catches {@link + * ConstraintViolationException} and transforms it into a {@link + * io.jooby.validation.ValidationResult} * * @author kliushnichenko * @since 3.3.1 */ public class HibernateValidatorModule implements Extension { - - private static final String CONFIG_ROOT_PATH = "hibernate.validator"; - - private Consumer configurer; - private StatusCode statusCode = StatusCode.UNPROCESSABLE_ENTITY; - private String title = "Validation failed"; - private boolean disableDefaultViolationHandler = false; - - /** - * Setups a configurer callback. - * - * @param configurer Configurer callback. - * @return This module. - */ - public HibernateValidatorModule doWith(@NonNull final Consumer configurer) { - this.configurer = configurer; - return this; - } - - /** - * Overrides the default status code for the errors produced by validation. - * Default code is UNPROCESSABLE_ENTITY(422) - * - * @param statusCode new status code - * @return This module. - */ - public HibernateValidatorModule statusCode(@NonNull StatusCode statusCode) { - this.statusCode = statusCode; - return this; + private static final String CONFIG_ROOT_PATH = "hibernate.validator"; + private Consumer configurer; + private StatusCode statusCode = StatusCode.UNPROCESSABLE_ENTITY; + private String title = "Validation failed"; + private boolean disableDefaultViolationHandler = false; + + /** + * Setups a configurer callback. + * + * @param configurer Configurer callback. + * @return This module. + */ + public HibernateValidatorModule doWith( + @NonNull final Consumer configurer) { + this.configurer = configurer; + return this; + } + + /** + * Overrides the default status code for the errors produced by validation. Default code is + * UNPROCESSABLE_ENTITY(422) + * + * @param statusCode new status code + * @return This module. + */ + public HibernateValidatorModule statusCode(@NonNull StatusCode statusCode) { + this.statusCode = statusCode; + return this; + } + + /** + * Overrides the default title for the errors produced by validation. Default title is "Validation + * failed" + * + * @param title new title + * @return This module. + */ + public HibernateValidatorModule validationTitle(@NonNull String title) { + this.title = title; + return this; + } + + /** + * Disables default constraint violation handler. By default {@link HibernateValidatorModule} + * provides built-in error handler for the {@link ConstraintViolationException} Such exceptions + * are transformed into response of {@link io.jooby.validation.ValidationResult} Use this flag to + * disable default error handler and provide your custom. + * + * @return This module. + */ + public HibernateValidatorModule disableViolationHandler() { + this.disableDefaultViolationHandler = true; + return this; + } + + @Override + public void install(@NonNull Jooby app) throws Exception { + var config = app.getConfig(); + var hbvConfig = byProvider(HibernateValidator.class).configure(); + + if (config.hasPath(CONFIG_ROOT_PATH)) { + config + .getConfig(CONFIG_ROOT_PATH) + .root() + .forEach( + (k, v) -> + hbvConfig.addProperty(CONFIG_ROOT_PATH + "." + k, v.unwrapped().toString())); } - /** - * Overrides the default title for the errors produced by validation. - * Default title is "Validation failed" - * - * @param title new title - * @return This module. - */ - public HibernateValidatorModule validationTitle(@NonNull String title) { - this.title = title; - return this; + if (configurer != null) { + configurer.accept(hbvConfig); } - /** - * Disables default constraint violation handler. - * By default {@link HibernateValidatorModule} provides - * built-in error handler for the {@link ConstraintViolationException} - * Such exceptions are transformed into response of {@link io.jooby.validation.ValidationResult} - * Use this flag to disable default error handler and provide your custom. - * - * @return This module. - */ - public HibernateValidatorModule disableViolationHandler() { - this.disableDefaultViolationHandler = true; - return this; - } + try (var factory = hbvConfig.buildValidatorFactory()) { + Validator validator = factory.getValidator(); + app.getServices().put(Validator.class, validator); + app.getServices().put(MvcValidator.class, new MvcValidatorImpl(validator)); - @Override - public void install(@NonNull Jooby app) throws Exception { - Config config = app.getConfig(); - HibernateValidatorConfiguration hbvConfig = byProvider(HibernateValidator.class).configure(); - - if (config.hasPath(CONFIG_ROOT_PATH)) { - config.getConfig(CONFIG_ROOT_PATH) - .root() - .forEach((k, v) -> hbvConfig.addProperty(CONFIG_ROOT_PATH + "." + k, v.unwrapped().toString())); - } - - if (configurer != null) { - configurer.accept(hbvConfig); - } - - try (ValidatorFactory factory = hbvConfig.buildValidatorFactory()) { - Validator validator = factory.getValidator(); - app.getServices().put(Validator.class, validator); - app.getServices().put(MvcValidator.class, new MvcValidatorImpl(validator)); - - if (!disableDefaultViolationHandler) { - app.error(ConstraintViolationException.class, new ConstraintViolationHandler(statusCode, title)); - } - } + if (!disableDefaultViolationHandler) { + app.error( + ConstraintViolationException.class, new ConstraintViolationHandler(statusCode, title)); + } } + } - static class MvcValidatorImpl implements MvcValidator { + static class MvcValidatorImpl implements MvcValidator { - private final Validator validator; + private final Validator validator; - MvcValidatorImpl(Validator validator) { - this.validator = validator; - } + MvcValidatorImpl(Validator validator) { + this.validator = validator; + } - @Override - public void validate(Context ctx, Object bean) throws ConstraintViolationException { - Set> violations = validator.validate(bean); - if (!violations.isEmpty()) { - throw new ConstraintViolationException(violations); - } - } + @Override + public void validate(Context ctx, Object bean) throws ConstraintViolationException { + var violations = validator.validate(bean); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } } + } } diff --git a/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/package-info.java b/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/package-info.java index 81b5073c5b..5ae7c0a1c8 100644 --- a/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/package-info.java +++ b/modules/jooby-hibernate-validator/src/main/java/io/jooby/hibernate/validator/package-info.java @@ -1 +1,28 @@ +/** + * Hibernate Validator Module: https://jooby.io/modules/hibernate-validator. + * + *

{@code
+ * {
+ *   install(new HibernateValidatorModule());
+ *
+ * }
+ *
+ * public class Controller {
+ *
+ *   @POST("/create")
+ *   public void create(@Valid Bean bean) {
+ *   }
+ *
+ * }
+ * }
+ * + *

Supports validation of a single bean, list, array, or map. + * + *

The module also provides a built-in error handler that catches {@link + * jakarta.validation.ConstraintViolationException} and transforms it into a {@link + * io.jooby.validation.ValidationResult} + * + * @author kliushnichenko + * @since 3.3.1 + */ package io.jooby.hibernate.validator;