diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..e329e50825 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +continuation_indent_size = 4 +max_line_length = 100 + +[*.md] +trim_trailing_whitespace = false + +[*.adoc] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/docs/asciidoc/responses.adoc b/docs/asciidoc/responses.adoc index 5c5e3dd26d..33ae2f6a7f 100644 --- a/docs/asciidoc/responses.adoc +++ b/docs/asciidoc/responses.adoc @@ -241,7 +241,7 @@ there is a non-blocking route handler. <3> Value is provided from *event loop*. No blocking code is permitted <4> Value is computed/produces from completable future context -Running your `App` in *worker* mode works identically, except for we are able to do blocking calls: +Running your `App3508` in *worker* mode works identically, except for we are able to do blocking calls: .In worker mode [source,java,role="primary"] @@ -284,7 +284,7 @@ Running your `App` in *worker* mode works identically, except for we are able to <3> Value is provided from *worker mode*. Blocking code is permitted <4> Value is computed/produces from completable future context -Running your `App` in *default* mode works identically to running in the *event loop* mode: +Running your `App3508` in *default* mode works identically to running in the *event loop* mode: .In default mode [source,java,role="primary"] diff --git a/docs/asciidoc/routing.adoc b/docs/asciidoc/routing.adoc index 89c6334395..d258a19c5d 100644 --- a/docs/asciidoc/routing.adoc +++ b/docs/asciidoc/routing.adoc @@ -1274,7 +1274,7 @@ class App: Kooby({ <2> Imports all routes, services, callbacks, etc... from `Bar`. Output: `/bar` => `/bar` -This operator lets you for example to deploy `Foo` as a standalone application or integrate it into a main one called `App`. +This operator lets you for example to deploy `Foo` as a standalone application or integrate it into a main one called `App3508`. The install operator shares the state of the main application, so lazy initialization (and therefore _instantiation_) of any child applications is *mandatory*. diff --git a/modules/jooby-avaje-validator/pom.xml b/modules/jooby-avaje-validator/pom.xml index 7d7c8ee089..4da876c1af 100644 --- a/modules/jooby-avaje-validator/pom.xml +++ b/modules/jooby-avaje-validator/pom.xml @@ -45,11 +45,6 @@ jooby-apt test - - io.avaje - avaje-validator-generator - test - io.jooby 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 deleted file mode 100644 index c254cede14..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/AvajeValidatorModuleTest.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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.StatusCode.UNPROCESSABLE_ENTITY_CODE; -import static io.jooby.avaje.validator.app.App.DEFAULT_TITLE; -import static io.restassured.RestAssured.given; - -import java.util.List; -import java.util.Map; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -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; - -@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 deleted file mode 100644 index 68235e9449..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/App.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Jooby https://jooby.io - * Apache License Version 2.0 https://jooby.io/LICENSE.txt - * Copyright 2014 Edgar Espina - */ -package io.jooby.avaje.validator.app; - -import io.jooby.Jooby; -import io.jooby.StatusCode; -import io.jooby.avaje.validator.AvajeValidatorModule; -import io.jooby.jackson.JacksonModule; - -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().validationTitle(DEFAULT_TITLE).statusCode(STATUS_CODE)); - - mvc(new Controller()); - } -} 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 deleted file mode 100644 index 7631f7bf5e..0000000000 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Controller.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Jooby https://jooby.io - * Apache License Version 2.0 https://jooby.io/LICENSE.txt - * Copyright 2014 Edgar Espina - */ -package io.jooby.avaje.validator.app; - -import java.util.List; -import java.util.Map; - -import io.jooby.annotation.POST; -import io.jooby.annotation.Path; -import jakarta.validation.Valid; - -@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-hibernate-validator/pom.xml b/modules/jooby-hibernate-validator/pom.xml index e97df77450..ce6b4fce7d 100644 --- a/modules/jooby-hibernate-validator/pom.xml +++ b/modules/jooby-hibernate-validator/pom.xml @@ -38,20 +38,6 @@ 5.0.0 - - - io.jooby - jooby-netty - ${jooby.version} - test - - - io.jooby - jooby-jackson - ${jooby.version} - test - - org.junit.jupiter junit-jupiter-api @@ -70,19 +56,6 @@ ${jooby.version} test - - - io.rest-assured - rest-assured - test - - - - org.assertj - assertj-core - 3.26.3 - test - diff --git a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/HibernateValidatorModuleTest.java b/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/HibernateValidatorModuleTest.java deleted file mode 100644 index 9da14767d5..0000000000 --- a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/HibernateValidatorModuleTest.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.StatusCode.UNPROCESSABLE_ENTITY_CODE; -import static io.jooby.hibernate.validator.app.App.DEFAULT_TITLE; -import static io.restassured.RestAssured.given; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -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; - -@JoobyTest(value = App.class, port = 8099) -public class HibernateValidatorModuleTest { - - 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-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/App.java b/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/App.java deleted file mode 100644 index 7c5f7b9481..0000000000 --- a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/App.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Jooby https://jooby.io - * Apache License Version 2.0 https://jooby.io/LICENSE.txt - * Copyright 2014 Edgar Espina - */ -package io.jooby.hibernate.validator.app; - -import io.jooby.Jooby; -import io.jooby.StatusCode; -import io.jooby.hibernate.validator.HibernateValidatorModule; -import io.jooby.jackson.JacksonModule; - -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 HibernateValidatorModule().validationTitle(DEFAULT_TITLE).statusCode(STATUS_CODE)); - - mvc(new Controller()); - } -} diff --git a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java b/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java deleted file mode 100644 index 626760cb7b..0000000000 --- a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/NewAccountRequest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Jooby https://jooby.io - * Apache License Version 2.0 https://jooby.io/LICENSE.txt - * Copyright 2014 Edgar Espina - */ -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-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java b/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java deleted file mode 100644 index 38c766e1b6..0000000000 --- a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/Person.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Jooby https://jooby.io - * Apache License Version 2.0 https://jooby.io/LICENSE.txt - * Copyright 2014 Edgar Espina - */ -package io.jooby.hibernate.validator.app; - -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -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/tests/pom.xml b/tests/pom.xml index c4880310cf..e99b33a9de 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -108,6 +108,11 @@ jooby-mutiny ${jooby.version} + + io.jooby + jooby-avaje-validator + ${jooby.version} + io.jooby jooby-test @@ -125,6 +130,11 @@ commons-io commons-io + + io.avaje + avaje-validator-generator + test + org.jetbrains.kotlin diff --git a/tests/src/test/java/io/jooby/i3508/App3508.java b/tests/src/test/java/io/jooby/i3508/App3508.java new file mode 100644 index 0000000000..c11b3b7591 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3508/App3508.java @@ -0,0 +1,19 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3508; + +import io.jooby.Extension; +import io.jooby.Jooby; +import io.jooby.jackson.JacksonModule; + +public class App3508 extends Jooby { + public App3508(Extension validator) { + install(new JacksonModule()); + install(validator); + + mvc(new Controller3508_()); + } +} diff --git a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java b/tests/src/test/java/io/jooby/i3508/Controller3508.java similarity index 85% rename from modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java rename to tests/src/test/java/io/jooby/i3508/Controller3508.java index b90d57f3ff..86a68ec838 100644 --- a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/Controller.java +++ b/tests/src/test/java/io/jooby/i3508/Controller3508.java @@ -3,17 +3,19 @@ * Apache License Version 2.0 https://jooby.io/LICENSE.txt * Copyright 2014 Edgar Espina */ -package io.jooby.hibernate.validator.app; +package io.jooby.i3508; import java.util.List; import java.util.Map; import io.jooby.annotation.POST; import io.jooby.annotation.Path; +import io.jooby.i3508.data.NewAccountRequest; +import io.jooby.i3508.data.Person; import jakarta.validation.Valid; @Path("") -public class Controller { +public class Controller3508 { @POST("/create-person") public void createPerson(@Valid Person person) {} diff --git a/tests/src/test/java/io/jooby/i3508/Issue3508.java b/tests/src/test/java/io/jooby/i3508/Issue3508.java index 182a8168ea..866036021d 100644 --- a/tests/src/test/java/io/jooby/i3508/Issue3508.java +++ b/tests/src/test/java/io/jooby/i3508/Issue3508.java @@ -5,19 +5,37 @@ */ package io.jooby.i3508; +import static io.jooby.StatusCode.UNPROCESSABLE_ENTITY_CODE; import static io.jooby.validation.BeanValidator.validate; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.List; +import java.util.Map; + +import org.assertj.core.api.Assertions; + +import com.fasterxml.jackson.databind.json.JsonMapper; +import io.jooby.Extension; import io.jooby.StatusCode; +import io.jooby.avaje.validator.AvajeValidatorModule; import io.jooby.hibernate.validator.HibernateValidatorModule; +import io.jooby.i3508.data.AvajeNewAccountRequest; +import io.jooby.i3508.data.HbvNewAccountRequest; +import io.jooby.i3508.data.NewAccountRequest; +import io.jooby.i3508.data.Person; import io.jooby.jackson.JacksonModule; import io.jooby.junit.ServerTest; import io.jooby.junit.ServerTestRunner; import io.jooby.validation.BeanValidator; +import io.jooby.validation.ValidationResult; import okhttp3.MediaType; import okhttp3.RequestBody; public class Issue3508 { + private static final StatusCode STATUS_CODE = StatusCode.UNPROCESSABLE_ENTITY; + public static final String DEFAULT_TITLE = "Validation failed"; + @ServerTest public void shouldValidateUsingProxy(ServerTestRunner runner) { runner @@ -53,4 +71,167 @@ public void shouldValidateUsingProxy(ServerTestRunner runner) { }); }); } + + @ServerTest + public void avajeValidatorTest(ServerTestRunner runner) { + validatorTest( + runner, + new AvajeValidatorModule().validationTitle(DEFAULT_TITLE).statusCode(STATUS_CODE), + new AvajeNewAccountRequest()); + } + + @ServerTest + public void hibernateValidatorTest(ServerTestRunner runner) { + validatorTest( + runner, + new HibernateValidatorModule().validationTitle(DEFAULT_TITLE).statusCode(STATUS_CODE), + new HbvNewAccountRequest()); + } + + private void validatorTest( + ServerTestRunner runner, Extension extension, NewAccountRequest request) { + var sizeLabel = extension instanceof HibernateValidatorModule ? "size" : "length"; + var json = JsonMapper.builder().build(); + runner + .use(() -> new App3508(extension)) + .ready( + http -> { + http.post( + "/create-person", + RequestBody.create( + json.writeValueAsBytes(new Person(null, "Last Name")), + MediaType.get("application/json")), + rsp -> { + assertEquals(UNPROCESSABLE_ENTITY_CODE, rsp.code()); + var actualResult = json.readValue(rsp.body().string(), ValidationResult.class); + var fieldError = + new ValidationResult.Error( + "firstName", + List.of("must not be empty"), + ValidationResult.ErrorType.FIELD); + var expectedResult = buildResult(List.of(fieldError)); + + assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + }); + + http.post( + "/create-array-of-persons", + RequestBody.create( + json.writeValueAsBytes( + List.of( + new Person("First Name", "Last Name"), + new Person(null, "Last Name 2"))), + MediaType.get("application/json")), + rsp -> { + assertEquals(UNPROCESSABLE_ENTITY_CODE, rsp.code()); + var actualResult = json.readValue(rsp.body().string(), ValidationResult.class); + + var fieldError = + new ValidationResult.Error( + "firstName", + List.of("must not be empty"), + ValidationResult.ErrorType.FIELD); + var expectedResult = buildResult(List.of(fieldError)); + + assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + }); + + http.post( + "/create-list-of-persons", + RequestBody.create( + json.writeValueAsBytes( + List.of( + new Person("First Name", "Last Name"), + new Person(null, "Last Name 2"))), + MediaType.get("application/json")), + rsp -> { + assertEquals(UNPROCESSABLE_ENTITY_CODE, rsp.code()); + var actualResult = json.readValue(rsp.body().string(), ValidationResult.class); + var fieldError = + new ValidationResult.Error( + "firstName", + List.of("must not be empty"), + ValidationResult.ErrorType.FIELD); + var expectedResult = buildResult(List.of(fieldError)); + + assertThat(expectedResult) + .usingRecursiveComparison() + .ignoringCollectionOrderInFieldsMatchingRegexes("errors\\.messages") + .isEqualTo(actualResult); + }); + + http.post( + "/create-map-of-persons", + RequestBody.create( + json.writeValueAsBytes( + Map.of( + new Person("First Name", "Last Name"), + new Person(null, "Last Name 2"))), + MediaType.get("application/json")), + rsp -> { + assertEquals(UNPROCESSABLE_ENTITY_CODE, rsp.code()); + var actualResult = json.readValue(rsp.body().string(), 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); + }); + + request.setLogin("jk"); + request.setPassword("123"); + request.setConfirmPassword("1234"); + request.setPerson(new Person(null, "Last Name")); + http.post( + "/create-new-account", + RequestBody.create( + json.writeValueAsBytes(request), MediaType.get("application/json")), + rsp -> { + assertEquals(UNPROCESSABLE_ENTITY_CODE, rsp.code()); + var actualResult = json.readValue(rsp.body().string(), ValidationResult.class); + var errors = + List.of( + new ValidationResult.Error( + "password", + List.of(sizeLabel + " 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(sizeLabel + " must be between 8 and 24"), + ValidationResult.ErrorType.FIELD), + new ValidationResult.Error( + "login", + List.of(sizeLabel + " 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/tests/src/test/java/io/jooby/i3508/data/AvajeNewAccountRequest.java b/tests/src/test/java/io/jooby/i3508/data/AvajeNewAccountRequest.java new file mode 100644 index 0000000000..87882d2763 --- /dev/null +++ b/tests/src/test/java/io/jooby/i3508/data/AvajeNewAccountRequest.java @@ -0,0 +1,12 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3508.data; + +import jakarta.validation.Valid; + +@Valid +@AvajePasswordsShouldMatch +public class AvajeNewAccountRequest extends NewAccountRequest {} diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatch.java b/tests/src/test/java/io/jooby/i3508/data/AvajePasswordsShouldMatch.java similarity index 88% rename from modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatch.java rename to tests/src/test/java/io/jooby/i3508/data/AvajePasswordsShouldMatch.java index dcb06956c8..4f0a85d374 100644 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatch.java +++ b/tests/src/test/java/io/jooby/i3508/data/AvajePasswordsShouldMatch.java @@ -3,7 +3,7 @@ * Apache License Version 2.0 https://jooby.io/LICENSE.txt * Copyright 2014 Edgar Espina */ -package io.jooby.avaje.validator.app; +package io.jooby.i3508.data; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; @@ -17,7 +17,7 @@ @Constraint(validatedBy = {}) @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) -public @interface PasswordsShouldMatch { +public @interface AvajePasswordsShouldMatch { String message() default "Passwords should match"; Class[] groups() default {}; diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatchValidator.java b/tests/src/test/java/io/jooby/i3508/data/AvajePasswordsShouldMatchValidator.java similarity index 69% rename from modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatchValidator.java rename to tests/src/test/java/io/jooby/i3508/data/AvajePasswordsShouldMatchValidator.java index 11d43f61c6..81a466f315 100644 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/PasswordsShouldMatchValidator.java +++ b/tests/src/test/java/io/jooby/i3508/data/AvajePasswordsShouldMatchValidator.java @@ -3,16 +3,17 @@ * Apache License Version 2.0 https://jooby.io/LICENSE.txt * Copyright 2014 Edgar Espina */ -package io.jooby.avaje.validator.app; +package io.jooby.i3508.data; 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 { +@ConstraintAdapter(AvajePasswordsShouldMatch.class) +public class AvajePasswordsShouldMatchValidator + extends AbstractConstraintAdapter { - public PasswordsShouldMatchValidator(AdapterCreateRequest request) { + public AvajePasswordsShouldMatchValidator(AdapterCreateRequest request) { super(request); } diff --git a/tests/src/test/java/io/jooby/i3508/data/HbvNewAccountRequest.java b/tests/src/test/java/io/jooby/i3508/data/HbvNewAccountRequest.java new file mode 100644 index 0000000000..af804929dc --- /dev/null +++ b/tests/src/test/java/io/jooby/i3508/data/HbvNewAccountRequest.java @@ -0,0 +1,12 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.i3508.data; + +import jakarta.validation.Valid; + +@Valid +@HbvPasswordsShouldMatch +public class HbvNewAccountRequest extends NewAccountRequest {} diff --git a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java b/tests/src/test/java/io/jooby/i3508/data/HbvPasswordsShouldMatch.java similarity index 81% rename from modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java rename to tests/src/test/java/io/jooby/i3508/data/HbvPasswordsShouldMatch.java index b2cb9aee5b..7f75ab3f5e 100644 --- a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatch.java +++ b/tests/src/test/java/io/jooby/i3508/data/HbvPasswordsShouldMatch.java @@ -3,7 +3,7 @@ * Apache License Version 2.0 https://jooby.io/LICENSE.txt * Copyright 2014 Edgar Espina */ -package io.jooby.hibernate.validator.app; +package io.jooby.i3508.data; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; @@ -15,10 +15,10 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; -@Constraint(validatedBy = PasswordsShouldMatchValidator.class) +@Constraint(validatedBy = HbvPasswordsShouldMatchValidator.class) @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) -public @interface PasswordsShouldMatch { +public @interface HbvPasswordsShouldMatch { String message() default "Passwords should match"; Class[] groups() default {}; diff --git a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java b/tests/src/test/java/io/jooby/i3508/data/HbvPasswordsShouldMatchValidator.java similarity index 76% rename from modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java rename to tests/src/test/java/io/jooby/i3508/data/HbvPasswordsShouldMatchValidator.java index 594659cf32..05dc3f7334 100644 --- a/modules/jooby-hibernate-validator/src/test/java/io/jooby/hibernate/validator/app/PasswordsShouldMatchValidator.java +++ b/tests/src/test/java/io/jooby/i3508/data/HbvPasswordsShouldMatchValidator.java @@ -3,13 +3,13 @@ * Apache License Version 2.0 https://jooby.io/LICENSE.txt * Copyright 2014 Edgar Espina */ -package io.jooby.hibernate.validator.app; +package io.jooby.i3508.data; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; -public class PasswordsShouldMatchValidator - implements ConstraintValidator { +public class HbvPasswordsShouldMatchValidator + implements ConstraintValidator { @Override public boolean isValid(NewAccountRequest request, ConstraintValidatorContext constraintContext) { diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/NewAccountRequest.java b/tests/src/test/java/io/jooby/i3508/data/NewAccountRequest.java similarity index 95% rename from modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/NewAccountRequest.java rename to tests/src/test/java/io/jooby/i3508/data/NewAccountRequest.java index 435d7656df..64fbd18a20 100644 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/NewAccountRequest.java +++ b/tests/src/test/java/io/jooby/i3508/data/NewAccountRequest.java @@ -3,7 +3,7 @@ * Apache License Version 2.0 https://jooby.io/LICENSE.txt * Copyright 2014 Edgar Espina */ -package io.jooby.avaje.validator.app; +package io.jooby.i3508.data; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; @@ -11,7 +11,6 @@ import jakarta.validation.constraints.Size; @Valid -@PasswordsShouldMatch public class NewAccountRequest { @NotNull @NotEmpty @Size(min = 3, max = 16) diff --git a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Person.java b/tests/src/test/java/io/jooby/i3508/data/Person.java similarity index 94% rename from modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Person.java rename to tests/src/test/java/io/jooby/i3508/data/Person.java index 260d67587d..3e1896a7cd 100644 --- a/modules/jooby-avaje-validator/src/test/java/io/jooby/avaje/validator/app/Person.java +++ b/tests/src/test/java/io/jooby/i3508/data/Person.java @@ -3,7 +3,7 @@ * Apache License Version 2.0 https://jooby.io/LICENSE.txt * Copyright 2014 Edgar Espina */ -package io.jooby.avaje.validator.app; +package io.jooby.i3508.data; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty;