From eba2414a1daa28e05b892974c31d77c6dd37cd40 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:06:42 -0400 Subject: [PATCH 1/4] working --- modules/jooby-avaje-validator/pom.xml | 112 ++++++++++ .../avaje/validator/AvajeValidatorModule.java | 176 +++++++++++++++ .../validator/ConstraintViolationHandler.java | 95 +++++++++ .../jooby/avaje/validator/package-info.java | 1 + .../src/main/java/module-info.java | 17 ++ .../validator/AvajeValidatorModuleTest.java | 200 ++++++++++++++++++ .../io/jooby/hibernate/validator/app/App.java | 24 +++ .../hibernate/validator/app/Controller.java | 32 +++ .../validator/app/NewAccountRequest.java | 59 ++++++ .../validator/app/PasswordsShouldMatch.java | 22 ++ .../app/PasswordsShouldMatchValidator.java | 15 ++ .../jooby/hibernate/validator/app/Person.java | 35 +++ pom.xml | 28 ++- 13 files changed, 810 insertions(+), 6 deletions(-) create mode 100644 modules/jooby-avaje-validator/pom.xml create mode 100644 modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/AvajeValidatorModule.java create mode 100644 modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/ConstraintViolationHandler.java create mode 100644 modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/package-info.java create mode 100644 modules/jooby-avaje-validator/src/main/java/module-info.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java diff --git a/modules/jooby-avaje-validator/pom.xml b/modules/jooby-avaje-validator/pom.xml new file mode 100644 index 0000000000..a2fa20eb51 --- /dev/null +++ b/modules/jooby-avaje-validator/pom.xml @@ -0,0 +1,112 @@ + + + + + io.jooby + modules + 3.3.1-SNAPSHOT + + + 4.0.0 + jooby-avaje-validator + + + + io.jooby + jooby + + + + io.jooby + jooby-validation + ${project.version} + + + + + io.avaje + avaje-validator + + + + jakarta.validation + jakarta.validation-api + + + + + io.jooby + jooby-netty + test + + + + io.jooby + jooby-jackson + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + io.jooby + jooby-test + test + + + + io.rest-assured + rest-assured + test + + + + org.assertj + assertj-core + 3.26.3 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + test + test-compile + + + + + -parameters + + + + io.jooby + jooby-apt + + + io.avaje + avaje-validator-generator + + + + + + + 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 new file mode 100644 index 0000000000..8b324edc2c --- /dev/null +++ b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/AvajeValidatorModule.java @@ -0,0 +1,176 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.avaje.validator; + +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.function.Consumer; + +import edu.umd.cs.findbugs.annotations.NonNull; +import io.avaje.validation.ConstraintViolationException; +import io.avaje.validation.Validator; +import io.jooby.Extension; +import io.jooby.Jooby; +import io.jooby.StatusCode; +import io.jooby.validation.MvcValidator; + +/** + * 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 + * ConstraintViolationException} and transforms it into a {@link + * io.jooby.validation.ValidationResult} + * + * @authors kliushnichenko, SentryMan + * @since 3.2.10 + */ +public class AvajeValidatorModule implements Extension { + + 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 AvajeValidatorModule 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 AvajeValidatorModule 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 AvajeValidatorModule validationTitle(@NonNull String title) { + this.title = title; + return this; + } + + /** + * Disables default constraint violation handler. By default {@link AvajeValidatorModule} 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 AvajeValidatorModule disableViolationHandler() { + this.disableDefaultViolationHandler = true; + return this; + } + + @Override + public void install(@NonNull Jooby app) throws Exception { + + var props = app.getEnvironment(); + + final var locales = new ArrayList(); + final var builder = Validator.builder(); + Optional.ofNullable(props.getProperty("validation.failFast", "false")) + .map(Boolean::valueOf) + .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) + .ifPresent( + l -> { + builder.setDefaultLocale(l); + locales.add(l); + }); + + Optional.ofNullable(props.getProperty("validation.locale.addedLocales")).stream() + .flatMap(s -> Arrays.stream(s.split(","))) + .map(Locale::forLanguageTag) + .forEach( + l -> { + builder.addLocales(l); + locales.add(l); + }); + + Optional.ofNullable(props.getProperty("validation.temporal.tolerance.value")) + .map(Long::valueOf) + .ifPresent( + duration -> { + final var unit = + Optional.ofNullable(props.getProperty("validation.temporal.tolerance.chronoUnit")) + .map(ChronoUnit::valueOf) + .orElse(ChronoUnit.MILLIS); + builder.temporalTolerance(Duration.of(duration, unit)); + }); + + if (configurer != null) { + configurer.accept(builder); + } + + Validator 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)); + } + } + + static class MvcValidatorImpl implements MvcValidator { + + private final Validator validator; + + MvcValidatorImpl(Validator validator) { + this.validator = validator; + } + + @Override + public void validate(Object bean) throws ConstraintViolationException { + validator.validate(bean); + } + } +} 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 new file mode 100644 index 0000000000..76181c7343 --- /dev/null +++ b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/ConstraintViolationHandler.java @@ -0,0 +1,95 @@ +package io.jooby.avaje.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.avaje.validation.ConstraintViolation; +import io.avaje.validation.ConstraintViolationException; +import io.jooby.Context; +import io.jooby.ErrorHandler; +import io.jooby.StatusCode; +import io.jooby.validation.ValidationResult; + +/** + * Catches and transform {@link ConstraintViolationException} into {@link ValidationResult} + * + *

Payload example: + * + *

{@code
+ * {
+ *    "title": "Validation failed",
+ *    "status": 422,
+ *    "errors": [
+ *       {
+ *          "field": null,
+ *          "messages": [
+ *             "Passwords should match"
+ *          ],
+ *          "type": "GLOBAL"
+ *       },
+ *       {
+ *          "field": "firstName",
+ *          "messages": [
+ *             "must not be empty",
+ *             "must not be null"
+ *          ],
+ *          "type": "FIELD"
+ *       }
+ *    ]
+ * }
+ * }
+ * + * @author kliushnichenko + * @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) { + this.statusCode = statusCode; + this.title = title; + } + + @Override + public void apply(@NonNull Context ctx, @NonNull Throwable cause, @NonNull StatusCode code) { + var ex = (ConstraintViolationException) cause; + + var violations = ex.violations(); + + 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); + } + + 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 extractMessages(List violations) { + return violations.stream().map(ConstraintViolation::message).toList(); + } +} 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 new file mode 100644 index 0000000000..aaf68dc3bb --- /dev/null +++ b/modules/jooby-avaje-validator/src/main/java/io/jooby/avaje/validator/package-info.java @@ -0,0 +1 @@ +package io.jooby.avaje.validator; diff --git a/modules/jooby-avaje-validator/src/main/java/module-info.java b/modules/jooby-avaje-validator/src/main/java/module-info.java new file mode 100644 index 0000000000..d6ecdf13b8 --- /dev/null +++ b/modules/jooby-avaje-validator/src/main/java/module-info.java @@ -0,0 +1,17 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +/** + * Avaje Validator Module. + */ +module io.jooby.avaje.validator { + exports io.jooby.avaje.validator; + + requires transitive io.jooby; + requires static com.github.spotbugs.annotations; + requires typesafe.config; + requires transitive io.avaje.validation; + requires transitive io.jooby.validation; +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java new file mode 100644 index 0000000000..b9034f3238 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java @@ -0,0 +1,200 @@ +package io.jooby.hibernate.validator; + +import io.jooby.hibernate.validator.app.App; +import io.jooby.hibernate.validator.app.NewAccountRequest; +import io.jooby.hibernate.validator.app.Person; +import io.jooby.test.JoobyTest; +import io.jooby.validation.ValidationResult; +import io.restassured.RestAssured; +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static io.jooby.StatusCode.UNPROCESSABLE_ENTITY_CODE; +import static io.jooby.hibernate.validator.app.App.DEFAULT_TITLE; +import static io.restassured.RestAssured.given; + +@JoobyTest(value = App.class, port = 8099) +public class AvajeValidatorModuleTest { + + protected static RequestSpecification SPEC = new RequestSpecBuilder() + .setPort(8099) + .setContentType(ContentType.JSON) + .setAccept(ContentType.JSON) + .build(); + + static { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + } + + @Test + public void validate_personBean_shouldDetect2Violations() { + Person person = new Person(null, "Last Name"); + + ValidationResult actualResult = given().spec(SPEC). + with() + .body(person) + .post("/create-person") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract().as(ValidationResult.class); + + var fieldError = new ValidationResult.Error( + "firstName", + List.of("must not be empty", "must not be null"), + ValidationResult.ErrorType.FIELD + ); + ValidationResult expectedResult = buildResult(List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_arrayOfPerson_shouldDetect2Violations() { + Person person1 = new Person("First Name", "Last Name"); + Person person2 = new Person(null, "Last Name 2"); + + ValidationResult actualResult = given().spec(SPEC). + with() + .body(new Person[]{person1, person2}) + .post("/create-array-of-persons") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract().as(ValidationResult.class); + + var fieldError = new ValidationResult.Error( + "firstName", + List.of("must not be empty", "must not be null"), + ValidationResult.ErrorType.FIELD + ); + ValidationResult expectedResult = buildResult(List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_listOfPerson_shouldDetect2Violations() { + Person person1 = new Person("First Name", "Last Name"); + Person person2 = new Person(null, "Last Name 2"); + + ValidationResult actualResult = given().spec(SPEC). + with() + .body(List.of(person1, person2)) + .post("/create-list-of-persons") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract().as(ValidationResult.class); + + var fieldError = new ValidationResult.Error( + "firstName", + List.of("must not be empty", "must not be null"), + ValidationResult.ErrorType.FIELD + ); + ValidationResult expectedResult = buildResult( List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_mapOfPerson_shouldDetect2Violations() { + Person person1 = new Person("First Name", "Last Name"); + Person person2 = new Person(null, "Last Name 2"); + + ValidationResult actualResult = given().spec(SPEC). + with() + .body(Map.of("1", person1, "2", person2)) + .post("/create-map-of-persons") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract().as(ValidationResult.class); + + var fieldError = new ValidationResult.Error( + "firstName", + List.of("must not be empty", "must not be null"), + ValidationResult.ErrorType.FIELD + ); + ValidationResult expectedResult = buildResult(List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_newAccountBean_shouldDetect6Violations() { + NewAccountRequest request = new NewAccountRequest(); + request.setLogin("jk"); + request.setPassword("123"); + request.setConfirmPassword("1234"); + request.setPerson(new Person(null, "Last Name")); + + ValidationResult actualResult = given().spec(SPEC). + with() + .body(request) + .post("/create-new-account") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract().as(ValidationResult.class); + + List errors = new ArrayList<>() {{ + add(new ValidationResult.Error( + null, + List.of("Passwords should match"), + ValidationResult.ErrorType.GLOBAL) + ); + add(new ValidationResult.Error( + "person.firstName", + List.of("must not be empty", "must not be null"), + ValidationResult.ErrorType.FIELD) + ); + add(new ValidationResult.Error( + "login", + List.of("size must be between 3 and 16"), + ValidationResult.ErrorType.FIELD) + ); + add(new ValidationResult.Error( + "password", + List.of("size must be between 8 and 24"), + ValidationResult.ErrorType.FIELD) + ); + add(new ValidationResult.Error( + "confirmPassword", + List.of("size must be between 8 and 24"), + ValidationResult.ErrorType.FIELD) + ); + }}; + + ValidationResult expectedResult = buildResult(errors); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors") + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + private ValidationResult buildResult(List errors) { + return new ValidationResult(DEFAULT_TITLE, UNPROCESSABLE_ENTITY_CODE, errors); + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java new file mode 100644 index 0000000000..d85a9ec216 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java @@ -0,0 +1,24 @@ +package io.jooby.hibernate.validator.app; + +import io.jooby.Jooby; +import io.jooby.StatusCode; +import io.jooby.avaje.validator.AvajeValidatorModule; +import io.jooby.avaje.validator.ConstraintViolationHandler; +import io.jooby.jackson.JacksonModule; +import jakarta.validation.ConstraintViolationException; + +public class App extends Jooby { + + private static final StatusCode STATUS_CODE = StatusCode.UNPROCESSABLE_ENTITY; + public static final String DEFAULT_TITLE = "Validation failed"; + + { + install(new JacksonModule()); + install(new AvajeValidatorModule()); + + mvc(new Controller()); + + error(ConstraintViolationException.class, new ConstraintViolationHandler(STATUS_CODE, DEFAULT_TITLE)); + } + +} \ No newline at end of file diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java new file mode 100644 index 0000000000..bf6ac84465 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java @@ -0,0 +1,32 @@ +package io.jooby.hibernate.validator.app; + +import io.jooby.annotation.POST; +import io.jooby.annotation.Path; +import jakarta.validation.Valid; + +import java.util.List; +import java.util.Map; + +@Path("") +public class Controller { + + @POST("/create-person") + public void createPerson(@Valid Person person) { + } + + @POST("/create-array-of-persons") + public void createArrayOfPersons(@Valid Person[] persons) { + } + + @POST("/create-list-of-persons") + public void createListOfPersons(@Valid List persons) { + } + + @POST("/create-map-of-persons") + public void createMapOfPersons(@Valid Map persons) { + } + + @POST("/create-new-account") + public void createNewAccount(@Valid NewAccountRequest request) { + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java new file mode 100644 index 0000000000..e67bcae8e4 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java @@ -0,0 +1,59 @@ +package io.jooby.hibernate.validator.app; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +@PasswordsShouldMatch +public class NewAccountRequest { + @NotNull + @NotEmpty + @Size(min = 3, max = 16) + private String login; + + @NotNull + @NotEmpty + @Size(min = 8, max = 24) + private String password; + + @NotNull + @NotEmpty + @Size(min = 8, max = 24) + private String confirmPassword; + + @Valid + private Person person; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getConfirmPassword() { + return confirmPassword; + } + + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java new file mode 100644 index 0000000000..dcd01c1852 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java @@ -0,0 +1,22 @@ +package io.jooby.hibernate.validator.app; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Constraint(validatedBy = PasswordsShouldMatchValidator.class) +@Target({TYPE, ANNOTATION_TYPE}) +@Retention(RUNTIME) +public @interface PasswordsShouldMatch { + String message() default "Passwords should match"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java new file mode 100644 index 0000000000..e1fbbf0eb4 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java @@ -0,0 +1,15 @@ +package io.jooby.hibernate.validator.app; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class PasswordsShouldMatchValidator implements ConstraintValidator { + + @Override + public boolean isValid(NewAccountRequest request, ConstraintValidatorContext constraintContext) { + if (request.getPassword() == null || request.getConfirmPassword() == null) { + return false; + } + return request.getPassword().equals(request.getConfirmPassword()); + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java new file mode 100644 index 0000000000..f0cbba3e71 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java @@ -0,0 +1,35 @@ +package io.jooby.hibernate.validator.app; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +@Valid +public class Person { + + @NotEmpty + @NotNull + private String firstName; + private String lastName; + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/pom.xml b/pom.xml index 870dd9dd6a..95dd5d737c 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,6 @@ 3.2.2 2.17.2 2.11.0 - 2.1 3.0.1 3.0.4 1.4.0 @@ -47,7 +46,6 @@ 1.4.3 - 10.3 7.0.0 @@ -80,6 +78,11 @@ 2.5.2 + + 10.3 + 2.1 + 2.1 + 2.0.1 3.1.0 @@ -673,26 +676,39 @@ io.avaje avaje-inject - ${avaje-inject.version} + ${avaje.inject.version} io.avaje avaje-inject-generator - ${avaje-inject.version} + ${avaje.inject.version} io.avaje avaje-jsonb - ${avaje-jsonb.version} + ${avaje.jsonb.version} io.avaje avaje-jsonb-generator - ${avaje-jsonb.version} + ${avaje.jsonb.version} + + + + + io.avaje + avaje-validator + ${avaje.validator.version} + + + + io.avaje + avaje-validator-generator + ${avaje.validator.version} From d099beb479e8cd35c97f1ce0152c7ab5a8a329ec Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:49:33 -0400 Subject: [PATCH 2/4] tests --- docs/asciidoc/modules/avaje-inject.adoc | 2 +- modules/jooby-avaje-inject/pom.xml | 35 +-- modules/jooby-avaje-validator/pom.xml | 29 +-- .../avaje/validator/AvajeValidatorModule.java | 5 +- .../validator/AvajeValidatorModuleTest.java | 197 +++++++++++++++++ .../io/jooby/avaje/validator/app/App.java | 25 +++ .../jooby/avaje/validator/app/Controller.java | 27 +++ .../validator/app/NewAccountRequest.java | 59 ++++++ .../validator/app/PasswordsShouldMatch.java | 10 +- .../app/PasswordsShouldMatchValidator.java | 21 ++ .../io/jooby/avaje/validator/app/Person.java | 33 +++ .../validator/AvajeValidatorModuleTest.java | 200 ------------------ .../io/jooby/hibernate/validator/app/App.java | 24 --- .../hibernate/validator/app/Controller.java | 32 --- .../validator/app/NewAccountRequest.java | 59 ------ .../app/PasswordsShouldMatchValidator.java | 15 -- .../jooby/hibernate/validator/app/Person.java | 35 --- .../validator/HibernateValidatorModule.java | 3 +- .../io/jooby/validation/BeanValidator.java | 24 +-- .../io/jooby/validation/MvcValidator.java | 5 +- modules/pom.xml | 1 + pom.xml | 6 + 22 files changed, 415 insertions(+), 432 deletions(-) create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/AvajeValidatorModuleTest.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/App.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Controller.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/NewAccountRequest.java rename modules/jooby-avaje-validator/src/test/java/io/jooby/{hibernate => avaje}/validator/app/PasswordsShouldMatch.java (63%) create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatchValidator.java create mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Person.java delete mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java delete mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java delete mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java delete mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java delete mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java delete mode 100644 modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java diff --git a/docs/asciidoc/modules/avaje-inject.adoc b/docs/asciidoc/modules/avaje-inject.adoc index eeb9913742..753e839515 100644 --- a/docs/asciidoc/modules/avaje-inject.adoc +++ b/docs/asciidoc/modules/avaje-inject.adoc @@ -21,7 +21,7 @@ io.avaje avaje-inject-generator - 10.0 + 10.3 diff --git a/modules/jooby-avaje-inject/pom.xml b/modules/jooby-avaje-inject/pom.xml index 5da897dfcf..30f4e6e877 100644 --- a/modules/jooby-avaje-inject/pom.xml +++ b/modules/jooby-avaje-inject/pom.xml @@ -11,7 +11,11 @@ 4.0.0 jooby-avaje-inject - + + + full + + com.github.spotbugs @@ -21,7 +25,6 @@ io.jooby jooby - ${jooby.version} @@ -33,7 +36,7 @@ io.avaje avaje-inject-generator - provided + test @@ -74,30 +77,4 @@ test - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - test - test-compile - - - - - -parameters - - - - io.avaje - avaje-inject-generator - - - - - - diff --git a/modules/jooby-avaje-validator/pom.xml b/modules/jooby-avaje-validator/pom.xml index a2fa20eb51..174a57ff50 100644 --- a/modules/jooby-avaje-validator/pom.xml +++ b/modules/jooby-avaje-validator/pom.xml @@ -11,7 +11,11 @@ 4.0.0 jooby-avaje-validator - + + + full + + io.jooby @@ -41,7 +45,18 @@ jooby-netty test - + + + io.jooby + jooby-apt + test + + + io.avaje + avaje-validator-generator + test + + io.jooby jooby-jackson @@ -95,16 +110,6 @@ -parameters - - - io.jooby - jooby-apt - - - io.avaje - avaje-validator-generator - - 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 8b324edc2c..a509da90bc 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 @@ -16,6 +16,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import io.avaje.validation.ConstraintViolationException; import io.avaje.validation.Validator; +import io.jooby.Context; import io.jooby.Extension; import io.jooby.Jooby; import io.jooby.StatusCode; @@ -169,8 +170,8 @@ static class MvcValidatorImpl implements MvcValidator { } @Override - public void validate(Object bean) throws ConstraintViolationException { - validator.validate(bean); + public void validate(Context ctx, Object bean) throws ConstraintViolationException { + validator.validate(bean, ctx.locale()); } } } diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/AvajeValidatorModuleTest.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/AvajeValidatorModuleTest.java new file mode 100644 index 0000000000..f1c9e74e6c --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/AvajeValidatorModuleTest.java @@ -0,0 +1,197 @@ +package io.jooby.avaje.validator; + +import io.jooby.avaje.validator.app.App; +import io.jooby.avaje.validator.app.NewAccountRequest; +import io.jooby.avaje.validator.app.Person; +import io.jooby.test.JoobyTest; +import io.jooby.validation.ValidationResult; +import io.restassured.RestAssured; +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static io.jooby.StatusCode.UNPROCESSABLE_ENTITY_CODE; +import static io.jooby.avaje.validator.app.App.DEFAULT_TITLE; +import static io.restassured.RestAssured.given; + +@JoobyTest(value = App.class, port = 8099) +public class AvajeValidatorModuleTest { + + protected static RequestSpecification SPEC = + new RequestSpecBuilder() + .setPort(8099) + .setContentType(ContentType.JSON) + .setAccept(ContentType.JSON) + .build(); + + static { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + } + + @Test + public void validate_personBean_shouldDetect2Violations() { + Person person = new Person(null, "Last Name"); + + ValidationResult actualResult = + given() + .spec(SPEC) + .with() + .body(person) + .post("/create-person") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract() + .as(ValidationResult.class); + + var fieldError = + new ValidationResult.Error( + "firstName", List.of("must not be empty"), ValidationResult.ErrorType.FIELD); + ValidationResult expectedResult = buildResult(List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_arrayOfPerson_shouldDetect2Violations() { + Person person1 = new Person("First Name", "Last Name"); + Person person2 = new Person(null, "Last Name 2"); + + ValidationResult actualResult = + given() + .spec(SPEC) + .with() + .body(new Person[] {person1, person2}) + .post("/create-array-of-persons") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract() + .as(ValidationResult.class); + + var fieldError = + new ValidationResult.Error( + "firstName", List.of("must not be empty"), ValidationResult.ErrorType.FIELD); + ValidationResult expectedResult = buildResult(List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_listOfPerson_shouldDetect2Violations() { + Person person1 = new Person("First Name", "Last Name"); + Person person2 = new Person(null, "Last Name 2"); + + ValidationResult actualResult = + given() + .spec(SPEC) + .with() + .body(List.of(person1, person2)) + .post("/create-list-of-persons") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract() + .as(ValidationResult.class); + + var fieldError = + new ValidationResult.Error( + "firstName", List.of("must not be empty"), ValidationResult.ErrorType.FIELD); + ValidationResult expectedResult = buildResult(List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_mapOfPerson_shouldDetect2Violations() { + Person person1 = new Person("First Name", "Last Name"); + Person person2 = new Person(null, "Last Name 2"); + + ValidationResult actualResult = + given() + .spec(SPEC) + .with() + .body(Map.of("1", person1, "2", person2)) + .post("/create-map-of-persons") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract() + .as(ValidationResult.class); + + var fieldError = + new ValidationResult.Error( + "firstName", List.of("must not be empty"), ValidationResult.ErrorType.FIELD); + ValidationResult expectedResult = buildResult(List.of(fieldError)); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + @Test + public void validate_newAccountBean_shouldDetect6Violations() { + NewAccountRequest request = new NewAccountRequest(); + request.setLogin("jk"); + request.setPassword("123"); + request.setConfirmPassword("1234"); + request.setPerson(new Person(null, "Last Name")); + + ValidationResult actualResult = + given() + .spec(SPEC) + .with() + .body(request) + .post("/create-new-account") + .then() + .assertThat() + .statusCode(UNPROCESSABLE_ENTITY_CODE) + .extract() + .as(ValidationResult.class); + + List errors = + List.of( + new ValidationResult.Error( + "password", + List.of("length must be between 8 and 24"), + ValidationResult.ErrorType.FIELD), + new ValidationResult.Error( + "person.firstName", List.of("must not be empty"), ValidationResult.ErrorType.FIELD), + new ValidationResult.Error( + "confirmPassword", + List.of("length must be between 8 and 24"), + ValidationResult.ErrorType.FIELD), + new ValidationResult.Error( + "login", + List.of("length must be between 3 and 16"), + ValidationResult.ErrorType.FIELD)); + + ValidationResult expectedResult = buildResult(errors); + + Assertions.assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors") + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + } + + private ValidationResult buildResult(List errors) { + return new ValidationResult(DEFAULT_TITLE, UNPROCESSABLE_ENTITY_CODE, errors); + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/App.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/App.java new file mode 100644 index 0000000000..885feaabfc --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/App.java @@ -0,0 +1,25 @@ +package io.jooby.avaje.validator.app; + +import io.jooby.Jooby; +import io.jooby.StatusCode; +import io.jooby.avaje.validator.AvajeValidatorModule; +import io.jooby.avaje.validator.ConstraintViolationHandler; +import io.jooby.jackson.JacksonModule; +import jakarta.validation.ConstraintViolationException; + +public class App extends Jooby { + + private static final StatusCode STATUS_CODE = StatusCode.UNPROCESSABLE_ENTITY; + public static final String DEFAULT_TITLE = "Validation failed"; + + { + install(new JacksonModule()); + install(new AvajeValidatorModule()); + + mvc(new Controller()); + + error( + ConstraintViolationException.class, + new ConstraintViolationHandler(STATUS_CODE, DEFAULT_TITLE)); + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Controller.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Controller.java new file mode 100644 index 0000000000..1adfb5ae11 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Controller.java @@ -0,0 +1,27 @@ +package io.jooby.avaje.validator.app; + +import io.jooby.annotation.POST; +import io.jooby.annotation.Path; +import jakarta.validation.Valid; + +import java.util.List; +import java.util.Map; + +@Path("") +public class Controller { + + @POST("/create-person") + public void createPerson(@Valid Person person) {} + + @POST("/create-array-of-persons") + public void createArrayOfPersons(@Valid Person[] persons) {} + + @POST("/create-list-of-persons") + public void createListOfPersons(@Valid List persons) {} + + @POST("/create-map-of-persons") + public void createMapOfPersons(@Valid Map persons) {} + + @POST("/create-new-account") + public void createNewAccount(@Valid NewAccountRequest request) {} +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/NewAccountRequest.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/NewAccountRequest.java new file mode 100644 index 0000000000..129618e7ce --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/NewAccountRequest.java @@ -0,0 +1,59 @@ +package io.jooby.avaje.validator.app; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +@Valid +@PasswordsShouldMatch +public class NewAccountRequest { + @NotNull + @NotEmpty + @Size(min = 3, max = 16) + private String login; + + @NotNull + @NotEmpty + @Size(min = 8, max = 24) + private String password; + + @NotNull + @NotEmpty + @Size(min = 8, max = 24) + private String confirmPassword; + + @Valid private Person person; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getConfirmPassword() { + return confirmPassword; + } + + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatch.java similarity index 63% rename from modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java rename to modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatch.java index dcd01c1852..ae11aea568 100644 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatch.java @@ -1,4 +1,4 @@ -package io.jooby.hibernate.validator.app; +package io.jooby.avaje.validator.app; import jakarta.validation.Constraint; import jakarta.validation.Payload; @@ -10,13 +10,11 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -@Constraint(validatedBy = PasswordsShouldMatchValidator.class) +@Constraint(validatedBy = {}) @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) public @interface PasswordsShouldMatch { - String message() default "Passwords should match"; + String message() default "Passwords should match"; - Class[] groups() default {}; - - Class[] payload() default {}; + Class[] groups() default {}; } diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatchValidator.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatchValidator.java new file mode 100644 index 0000000000..073c025057 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatchValidator.java @@ -0,0 +1,21 @@ +package io.jooby.avaje.validator.app; + +import io.avaje.validation.adapter.AbstractConstraintAdapter; +import io.avaje.validation.adapter.ConstraintAdapter; +import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest; + +@ConstraintAdapter(PasswordsShouldMatch.class) +public class PasswordsShouldMatchValidator extends AbstractConstraintAdapter { + + public PasswordsShouldMatchValidator(AdapterCreateRequest request) { + super(request); + } + + @Override + public boolean isValid(NewAccountRequest request) { + if (request.getPassword() == null || request.getConfirmPassword() == null) { + return false; + } + return request.getPassword().equals(request.getConfirmPassword()); + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Person.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Person.java new file mode 100644 index 0000000000..0687bc0ac1 --- /dev/null +++ b/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Person.java @@ -0,0 +1,33 @@ +package io.jooby.avaje.validator.app; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +@Valid +public class Person { + + @NotEmpty private String firstName; + private String lastName; + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java deleted file mode 100644 index b9034f3238..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/AvajeValidatorModuleTest.java +++ /dev/null @@ -1,200 +0,0 @@ -package io.jooby.hibernate.validator; - -import io.jooby.hibernate.validator.app.App; -import io.jooby.hibernate.validator.app.NewAccountRequest; -import io.jooby.hibernate.validator.app.Person; -import io.jooby.test.JoobyTest; -import io.jooby.validation.ValidationResult; -import io.restassured.RestAssured; -import io.restassured.builder.RequestSpecBuilder; -import io.restassured.http.ContentType; -import io.restassured.specification.RequestSpecification; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static io.jooby.StatusCode.UNPROCESSABLE_ENTITY_CODE; -import static io.jooby.hibernate.validator.app.App.DEFAULT_TITLE; -import static io.restassured.RestAssured.given; - -@JoobyTest(value = App.class, port = 8099) -public class AvajeValidatorModuleTest { - - protected static RequestSpecification SPEC = new RequestSpecBuilder() - .setPort(8099) - .setContentType(ContentType.JSON) - .setAccept(ContentType.JSON) - .build(); - - static { - RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); - } - - @Test - public void validate_personBean_shouldDetect2Violations() { - Person person = new Person(null, "Last Name"); - - ValidationResult actualResult = given().spec(SPEC). - with() - .body(person) - .post("/create-person") - .then() - .assertThat() - .statusCode(UNPROCESSABLE_ENTITY_CODE) - .extract().as(ValidationResult.class); - - var fieldError = new ValidationResult.Error( - "firstName", - List.of("must not be empty", "must not be null"), - ValidationResult.ErrorType.FIELD - ); - ValidationResult expectedResult = buildResult(List.of(fieldError)); - - Assertions.assertThat(expectedResult) - .usingRecursiveComparison() - .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") - .isEqualTo(actualResult); - } - - @Test - public void validate_arrayOfPerson_shouldDetect2Violations() { - Person person1 = new Person("First Name", "Last Name"); - Person person2 = new Person(null, "Last Name 2"); - - ValidationResult actualResult = given().spec(SPEC). - with() - .body(new Person[]{person1, person2}) - .post("/create-array-of-persons") - .then() - .assertThat() - .statusCode(UNPROCESSABLE_ENTITY_CODE) - .extract().as(ValidationResult.class); - - var fieldError = new ValidationResult.Error( - "firstName", - List.of("must not be empty", "must not be null"), - ValidationResult.ErrorType.FIELD - ); - ValidationResult expectedResult = buildResult(List.of(fieldError)); - - Assertions.assertThat(expectedResult) - .usingRecursiveComparison() - .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") - .isEqualTo(actualResult); - } - - @Test - public void validate_listOfPerson_shouldDetect2Violations() { - Person person1 = new Person("First Name", "Last Name"); - Person person2 = new Person(null, "Last Name 2"); - - ValidationResult actualResult = given().spec(SPEC). - with() - .body(List.of(person1, person2)) - .post("/create-list-of-persons") - .then() - .assertThat() - .statusCode(UNPROCESSABLE_ENTITY_CODE) - .extract().as(ValidationResult.class); - - var fieldError = new ValidationResult.Error( - "firstName", - List.of("must not be empty", "must not be null"), - ValidationResult.ErrorType.FIELD - ); - ValidationResult expectedResult = buildResult( List.of(fieldError)); - - Assertions.assertThat(expectedResult) - .usingRecursiveComparison() - .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") - .isEqualTo(actualResult); - } - - @Test - public void validate_mapOfPerson_shouldDetect2Violations() { - Person person1 = new Person("First Name", "Last Name"); - Person person2 = new Person(null, "Last Name 2"); - - ValidationResult actualResult = given().spec(SPEC). - with() - .body(Map.of("1", person1, "2", person2)) - .post("/create-map-of-persons") - .then() - .assertThat() - .statusCode(UNPROCESSABLE_ENTITY_CODE) - .extract().as(ValidationResult.class); - - var fieldError = new ValidationResult.Error( - "firstName", - List.of("must not be empty", "must not be null"), - ValidationResult.ErrorType.FIELD - ); - ValidationResult expectedResult = buildResult(List.of(fieldError)); - - Assertions.assertThat(expectedResult) - .usingRecursiveComparison() - .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") - .isEqualTo(actualResult); - } - - @Test - public void validate_newAccountBean_shouldDetect6Violations() { - NewAccountRequest request = new NewAccountRequest(); - request.setLogin("jk"); - request.setPassword("123"); - request.setConfirmPassword("1234"); - request.setPerson(new Person(null, "Last Name")); - - ValidationResult actualResult = given().spec(SPEC). - with() - .body(request) - .post("/create-new-account") - .then() - .assertThat() - .statusCode(UNPROCESSABLE_ENTITY_CODE) - .extract().as(ValidationResult.class); - - List errors = new ArrayList<>() {{ - add(new ValidationResult.Error( - null, - List.of("Passwords should match"), - ValidationResult.ErrorType.GLOBAL) - ); - add(new ValidationResult.Error( - "person.firstName", - List.of("must not be empty", "must not be null"), - ValidationResult.ErrorType.FIELD) - ); - add(new ValidationResult.Error( - "login", - List.of("size must be between 3 and 16"), - ValidationResult.ErrorType.FIELD) - ); - add(new ValidationResult.Error( - "password", - List.of("size must be between 8 and 24"), - ValidationResult.ErrorType.FIELD) - ); - add(new ValidationResult.Error( - "confirmPassword", - List.of("size must be between 8 and 24"), - ValidationResult.ErrorType.FIELD) - ); - }}; - - ValidationResult expectedResult = buildResult(errors); - - Assertions.assertThat(expectedResult) - .usingRecursiveComparison() - .ignoringCollectionOrderInFieldsMatchingRegexes("errors") - .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") - .isEqualTo(actualResult); - } - - private ValidationResult buildResult(List errors) { - return new ValidationResult(DEFAULT_TITLE, UNPROCESSABLE_ENTITY_CODE, errors); - } -} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java deleted file mode 100644 index d85a9ec216..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/App.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.jooby.hibernate.validator.app; - -import io.jooby.Jooby; -import io.jooby.StatusCode; -import io.jooby.avaje.validator.AvajeValidatorModule; -import io.jooby.avaje.validator.ConstraintViolationHandler; -import io.jooby.jackson.JacksonModule; -import jakarta.validation.ConstraintViolationException; - -public class App extends Jooby { - - private static final StatusCode STATUS_CODE = StatusCode.UNPROCESSABLE_ENTITY; - public static final String DEFAULT_TITLE = "Validation failed"; - - { - install(new JacksonModule()); - install(new AvajeValidatorModule()); - - mvc(new Controller()); - - error(ConstraintViolationException.class, new ConstraintViolationHandler(STATUS_CODE, DEFAULT_TITLE)); - } - -} \ No newline at end of file diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java deleted file mode 100644 index bf6ac84465..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.jooby.hibernate.validator.app; - -import io.jooby.annotation.POST; -import io.jooby.annotation.Path; -import jakarta.validation.Valid; - -import java.util.List; -import java.util.Map; - -@Path("") -public class Controller { - - @POST("/create-person") - public void createPerson(@Valid Person person) { - } - - @POST("/create-array-of-persons") - public void createArrayOfPersons(@Valid Person[] persons) { - } - - @POST("/create-list-of-persons") - public void createListOfPersons(@Valid List persons) { - } - - @POST("/create-map-of-persons") - public void createMapOfPersons(@Valid Map persons) { - } - - @POST("/create-new-account") - public void createNewAccount(@Valid NewAccountRequest request) { - } -} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java deleted file mode 100644 index e67bcae8e4..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.jooby.hibernate.validator.app; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; - -@PasswordsShouldMatch -public class NewAccountRequest { - @NotNull - @NotEmpty - @Size(min = 3, max = 16) - private String login; - - @NotNull - @NotEmpty - @Size(min = 8, max = 24) - private String password; - - @NotNull - @NotEmpty - @Size(min = 8, max = 24) - private String confirmPassword; - - @Valid - private Person person; - - public String getLogin() { - return login; - } - - public void setLogin(String login) { - this.login = login; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getConfirmPassword() { - return confirmPassword; - } - - public void setConfirmPassword(String confirmPassword) { - this.confirmPassword = confirmPassword; - } - - public Person getPerson() { - return person; - } - - public void setPerson(Person person) { - this.person = person; - } -} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java deleted file mode 100644 index e1fbbf0eb4..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.jooby.hibernate.validator.app; - -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; - -public class PasswordsShouldMatchValidator implements ConstraintValidator { - - @Override - public boolean isValid(NewAccountRequest request, ConstraintValidatorContext constraintContext) { - if (request.getPassword() == null || request.getConfirmPassword() == null) { - return false; - } - return request.getPassword().equals(request.getConfirmPassword()); - } -} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java b/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java deleted file mode 100644 index f0cbba3e71..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.jooby.hibernate.validator.app; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -@Valid -public class Person { - - @NotEmpty - @NotNull - private String firstName; - private String lastName; - - public Person(String firstName, String lastName) { - this.firstName = firstName; - this.lastName = lastName; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } -} 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 970191425b..73cc55a541 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 @@ -7,6 +7,7 @@ import com.typesafe.config.Config; import edu.umd.cs.findbugs.annotations.NonNull; +import io.jooby.Context; import io.jooby.Extension; import io.jooby.Jooby; import io.jooby.StatusCode; @@ -142,7 +143,7 @@ static class MvcValidatorImpl implements MvcValidator { } @Override - public void validate(Object bean) throws ConstraintViolationException { + public void validate(Context ctx, Object bean) throws ConstraintViolationException { Set> violations = validator.validate(bean); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); diff --git a/modules/jooby-validation/src/main/java/io/jooby/validation/BeanValidator.java b/modules/jooby-validation/src/main/java/io/jooby/validation/BeanValidator.java index d445ff20d0..4d81969f9d 100644 --- a/modules/jooby-validation/src/main/java/io/jooby/validation/BeanValidator.java +++ b/modules/jooby-validation/src/main/java/io/jooby/validation/BeanValidator.java @@ -20,33 +20,27 @@ */ public final class BeanValidator { + private BeanValidator() {} + public static T validate(Context ctx, T bean) { MvcValidator validator = ctx.require(MvcValidator.class); if (bean instanceof Collection) { - validateCollection(validator, (Collection) bean); + validateCollection(validator, ctx, (Collection) bean); } else if (bean.getClass().isArray()) { - validateCollection(validator, Arrays.asList((Object[]) bean)); + validateCollection(validator, ctx, Arrays.asList((Object[]) bean)); } else if (bean instanceof Map) { - validateCollection(validator, ((Map) bean).values()); + validateCollection(validator, ctx, ((Map) bean).values()); } else { - validateObject(validator, bean); + validator.validate(ctx, bean); } return bean; } - private static void validateCollection(MvcValidator validator, Collection beans) { - for (Object item : beans) { - validateObject(validator, item); - } - } - - private static void validateObject(MvcValidator validator, Object bean) { - try { - validator.validate(bean); - } catch (Throwable e) { - SneakyThrows.propagate(e); + private static void validateCollection(MvcValidator validator, Context ctx, Collection beans) { + for (var item : beans) { + validator.validate(ctx, item); } } } diff --git a/modules/jooby-validation/src/main/java/io/jooby/validation/MvcValidator.java b/modules/jooby-validation/src/main/java/io/jooby/validation/MvcValidator.java index d17b7b6eb7..7fcca35fbb 100644 --- a/modules/jooby-validation/src/main/java/io/jooby/validation/MvcValidator.java +++ b/modules/jooby-validation/src/main/java/io/jooby/validation/MvcValidator.java @@ -1,5 +1,7 @@ package io.jooby.validation; +import io.jooby.Context; + /** * This interface should be implemented by modules that provide bean validation functionality. * An instance of this interface must be registered in the Jooby service registry. @@ -11,7 +13,8 @@ public interface MvcValidator { /** * Method should validate the bean and throw an exception if any constraint violations are detected * @param bean bean to be validated + * @param ctx request context * @throws RuntimeException an exception with violations to be thrown (e.g. ConstraintViolationException) */ - void validate(Object bean) throws RuntimeException; + void validate(Context ctx, Object bean) throws RuntimeException; } diff --git a/modules/pom.xml b/modules/pom.xml index 3369be6e33..d18d100347 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -60,6 +60,7 @@ jooby-validation + jooby-avaje-validator jooby-hibernate-validator jooby-pac4j diff --git a/pom.xml b/pom.xml index 95dd5d737c..18ffc63809 100644 --- a/pom.xml +++ b/pom.xml @@ -349,6 +349,12 @@ ${jooby.version} + + io.jooby + jooby-avaje-validator + ${jooby.version} + + io.jooby jooby-hibernate From f910c06ba662e4246ebb4d103e7b32d758a7a1b1 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:15:09 -0400 Subject: [PATCH 3/4] Create avaje-validator.adoc --- docs/asciidoc/modules/avaje-validator.adoc | 274 +++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 docs/asciidoc/modules/avaje-validator.adoc diff --git a/docs/asciidoc/modules/avaje-validator.adoc b/docs/asciidoc/modules/avaje-validator.adoc new file mode 100644 index 0000000000..5cbde7de88 --- /dev/null +++ b/docs/asciidoc/modules/avaje-validator.adoc @@ -0,0 +1,274 @@ +== Avaje Validator + +Bean validation via https://avaje.io/validator/[Avaje Validator]. + +=== Usage + +1) Add the dependency: + +[dependency, artifactId="jooby-avaje-validator"] +. + +2) 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()) +} +---- + +3) 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 beans) { // <3> + ... + } + + @POST("/validate-map") + public void validateMap(@Valid Map 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) : Unit { // <3> + ... + } + + @POST("/validate-map") + fun validateMap(@Valid beans: Map) : 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> 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); + })); +} +---- \ No newline at end of file From 77f5825304630fe3bb69a2961d0cc0334a887da4 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:03:51 -0400 Subject: [PATCH 4/4] PR comments --- docs/asciidoc/modules/avaje-validator.adoc | 41 ++++++++++++++++++- docs/asciidoc/modules/modules.adoc | 1 + modules/jooby-avaje-validator/pom.xml | 2 +- .../avaje/validator/AvajeValidatorModule.java | 2 +- .../validator/ConstraintViolationHandler.java | 2 +- .../validator/HibernateValidatorModule.java | 2 +- 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/docs/asciidoc/modules/avaje-validator.adoc b/docs/asciidoc/modules/avaje-validator.adoc index 5cbde7de88..542d646eeb 100644 --- a/docs/asciidoc/modules/avaje-validator.adoc +++ b/docs/asciidoc/modules/avaje-validator.adoc @@ -9,7 +9,44 @@ Bean validation via https://avaje.io/validator/[Avaje Validator]. [dependency, artifactId="jooby-avaje-validator"] . -2) Install +2) Configure annotation processor + +.Maven +[source, xml, role = "primary"] +---- + + + + org.apache.maven.plugins + maven-compiler-plugin + ... + + + + io.avaje + avaje-validator-generator + 2.1 + + + + + + +---- + +.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"] @@ -31,7 +68,7 @@ import io.jooby.avaje.validator.AvajeValidatorModule } ---- -3) Usage in MVC routes +4) Usage in MVC routes .Java [source,java,role="primary"] diff --git a/docs/asciidoc/modules/modules.adoc b/docs/asciidoc/modules/modules.adoc index 98537fc089..5a957840bf 100644 --- a/docs/asciidoc/modules/modules.adoc +++ b/docs/asciidoc/modules/modules.adoc @@ -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 diff --git a/modules/jooby-avaje-validator/pom.xml b/modules/jooby-avaje-validator/pom.xml index 174a57ff50..ae0ee8095e 100644 --- a/modules/jooby-avaje-validator/pom.xml +++ b/modules/jooby-avaje-validator/pom.xml @@ -28,7 +28,7 @@ ${project.version} - + io.avaje avaje-validator 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 a509da90bc..bdc50beae5 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 @@ -47,7 +47,7 @@ * io.jooby.validation.ValidationResult} * * @authors kliushnichenko, SentryMan - * @since 3.2.10 + * @since 3.3.1 */ public class AvajeValidatorModule implements Extension { 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 ad35352567..dcc5f16bbc 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 @@ -46,7 +46,7 @@ * } * * @author kliushnichenko - * @since 3.2.10 + * @since 3.3.1 */ public class ConstraintViolationHandler implements ErrorHandler { 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 73cc55a541..5f7a3afe95 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 @@ -48,7 +48,7 @@ * and transforms it into a {@link io.jooby.validation.ValidationResult}

* * @author kliushnichenko - * @since 3.2.10 + * @since 3.3.1 */ public class HibernateValidatorModule implements Extension {