diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java index 4a27a6fe8..9d3abde30 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/QuteTemplatingEngineAdapter.java @@ -19,6 +19,7 @@ public class QuteTemplatingEngineAdapter extends AbstractTemplatingEngineAdapter "additionalModelTypeAnnotations.qute", "beanValidation.qute", "beanValidationCore.qute", + "beanValidationInlineCore.qute", "beanValidationHeaderParams.qute", "bodyParams.qute", "enumClass.qute", diff --git a/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute b/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute index 8f1a18394..5948d8a68 100644 --- a/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute +++ b/client/deployment/src/main/resources/templates/libraries/microprofile/api.qute @@ -135,11 +135,11 @@ public interface {classname} { @org.jboss.resteasy.annotations.providers.multipart.MultipartForm {op.operationIdCamelCase}MultipartForm multipartForm{#if op.hasPathParams},{/if} {/if} {#if use-bean-validation} - {#for p in op.pathParams}@jakarta.validation.Valid {#include pathParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasQueryParams},{/if} - {#for p in op.queryParams}@jakarta.validation.Valid {#include queryParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasCookieParams},{/if} - {#for p in op.cookieParams}@jakarta.validation.Valid {#include cookieParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasHeaderParams},{/if} - {#for p in op.headerParams}@jakarta.validation.Valid {#include headerParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasBodyParam}, - {#for p in op.bodyParams}@jakarta.validation.Valid {#include bodyParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{/if} + {#for p in op.pathParams}{#include beanValidationInlineCore.qute param=p/}{#include pathParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasQueryParams},{/if} + {#for p in op.queryParams}{#include beanValidationInlineCore.qute param=p/}{#include queryParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasCookieParams},{/if} + {#for p in op.cookieParams}{#include beanValidationInlineCore.qute param=p/}{#include cookieParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasHeaderParams},{/if} + {#for p in op.headerParams}{#include beanValidationInlineCore.qute param=p/}{#include headerParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasBodyParam}, + {#for p in op.bodyParams}{#include beanValidationInlineCore.qute param=p/}{#include bodyParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{/if} {#else} {#for p in op.pathParams}{#include pathParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasQueryParams},{/if} {#for p in op.queryParams}{#include queryParams.qute param=p/}{#if p_hasNext}, {/if}{/for}{#if op.hasCookieParams},{/if} @@ -149,7 +149,7 @@ public interface {classname} { {/if} {#else} {#for p in op.allParams} - {#if use-bean-validation}@jakarta.validation.Valid {/if}{! + {#if use-bean-validation}{#include beanValidationInlineCore.qute param=p/}{/if}{! !}{#include pathParams.qute param=p/}{#include queryParams.qute param=p/}{#include bodyParams.qute param=p/}{#include headerParams.qute param=p/}{#include cookieParams.qute param=p/}{#if p_hasNext}, {/if} {/for}{/if} ); diff --git a/client/deployment/src/main/resources/templates/libraries/microprofile/beanValidationInlineCore.qute b/client/deployment/src/main/resources/templates/libraries/microprofile/beanValidationInlineCore.qute new file mode 100644 index 000000000..accba8091 --- /dev/null +++ b/client/deployment/src/main/resources/templates/libraries/microprofile/beanValidationInlineCore.qute @@ -0,0 +1 @@ +{#if p.required}@jakarta.validation.constraints.NotNull {/if}{#if p.pattern}@jakarta.validation.constraints.Pattern(regexp = "{p.pattern}") {/if}{#if p.minLength || p.minItems}@jakarta.validation.constraints.Size(min = {p.minLength}{p.minItems}) {/if}{#if p.maxLength || p.maxItems}@jakarta.validation.constraints.Size(max = {p.maxLength}{p.maxItems}) {/if}{#if p.isInteger}{#if p.minimum}@jakarta.validation.constraints.Min({p.minimum}) {/if}{#if p.maximum}@jakarta.validation.constraints.Max({p.maximum}) {/if}{/if}{#if p.isLong}{#if p.minimum}@jakarta.validation.constraints.Min({p.minimum}L) {/if}{#if p.maximum}@jakarta.validation.constraints.Max({p.maximum}L) {/if}{/if}{#if !p.isInteger && !p.isLong}{#if p.minimum}@jakarta.validation.constraints.DecimalMin("{p.minimum}") {/if}{#if p.maximum}@jakarta.validation.constraints.DecimalMax("{p.maximum}") {/if}{/if}{#if use-bean-validation}@jakarta.validation.Valid {/if} \ No newline at end of file diff --git a/client/integration-tests/bean-validation/src/main/openapi/issue-976.yaml b/client/integration-tests/bean-validation/src/main/openapi/issue-976.yaml new file mode 100644 index 000000000..4e1d21a8d --- /dev/null +++ b/client/integration-tests/bean-validation/src/main/openapi/issue-976.yaml @@ -0,0 +1,80 @@ +--- +openapi: 3.0.3 +info: + title: Test API + version: "1.0" +paths: + /{pathParam}: + post: + tags: + - ValidatedEndpointIssue976 + operationId: test + parameters: + - name: queryParam + in: query + schema: + $ref: '#/components/schemas/ValidatedObjectIssue976' + - name: pathParam + in: path + description: pathParam description + required: true + schema: + type: string + maxLength: 14 + minLength: 14 + pattern: '^[0-9]{14}$' + example: '19318085994179' + - name: headerParam + in: header + description: 'Header description' + required: false + schema: + maxLength: 32 + minLength: 32 + pattern: '^[a-z0-9]{32}$' + type: string + example: 3cfdad6e03c24d0ab7112dce75cdba35 + - name: cookieParam + in: cookie + required: true + schema: + type: string + minLength: 10 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ValidatedObjectIssue976' + responses: + "200": + description: OK + content: + text/plain: + schema: + type: string +components: + schemas: + ValidatedObjectIssue976: + type: object + description: Some object to be validated + required: + - id + - name + - secondName + - size + properties: + id: + type: integer + minimum: 1 + maximum: 100 + name: + type: string + pattern: "[a-zA-Z]*" + minLength: 1 + maxLength: 10 + secondName: + type: string + size: + type: number + minimum: 1.0 + maximum: 10.0 \ No newline at end of file diff --git a/client/integration-tests/bean-validation/src/main/resources/application.properties b/client/integration-tests/bean-validation/src/main/resources/application.properties index 0078b6e34..06930b256 100644 --- a/client/integration-tests/bean-validation/src/main/resources/application.properties +++ b/client/integration-tests/bean-validation/src/main/resources/application.properties @@ -1,3 +1,4 @@ quarkus.openapi-generator.codegen.spec.bean_validation_true_yaml.use-bean-validation = true +quarkus.openapi-generator.codegen.spec.issue_976_yaml.use-bean-validation = true quarkus.openapi-generator.codegen.spec.bean_validation_false_yaml.use-bean-validation = false quarkus.keycloak.devservices.enabled=false \ No newline at end of file diff --git a/client/integration-tests/bean-validation/src/test/java/io/quarkiverse/openapi/generator/it/BeanValidationTest.java b/client/integration-tests/bean-validation/src/test/java/io/quarkiverse/openapi/generator/it/BeanValidationTest.java index 154ee8f3b..89ff485a9 100644 --- a/client/integration-tests/bean-validation/src/test/java/io/quarkiverse/openapi/generator/it/BeanValidationTest.java +++ b/client/integration-tests/bean-validation/src/test/java/io/quarkiverse/openapi/generator/it/BeanValidationTest.java @@ -5,6 +5,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.Arrays; import java.util.stream.Stream; @@ -16,12 +17,15 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.PathParam; import org.junit.jupiter.api.Test; import org.openapi.quarkus.bean_validation_false_yaml.api.UnvalidatedEndpointApi; import org.openapi.quarkus.bean_validation_false_yaml.model.UnvalidatedObject; import org.openapi.quarkus.bean_validation_true_yaml.api.ValidatedEndpointApi; import org.openapi.quarkus.bean_validation_true_yaml.model.ValidatedObject; +import org.openapi.quarkus.issue_976_yaml.api.ValidatedEndpointIssue976Api; import io.quarkus.test.junit.QuarkusTest; @@ -99,4 +103,37 @@ void testValidationAnnotationsAreSkippedModel() throws Exception { assertThat(size.isAnnotationPresent(DecimalMin.class)).isFalse(); assertThat(size.isAnnotationPresent(DecimalMax.class)).isFalse(); } + + @Test + void testValidationAnnotationsAreInPlaceApiIssue976() { + Method method = ValidatedEndpointIssue976Api.class.getMethods()[0]; + Annotation[][] annotationsPerParameter = method.getParameterAnnotations(); + + Parameter pathParam = Arrays.stream(method.getParameters()) + .filter(p -> p.getName().equals("pathParam")) + .findFirst().get(); + + Parameter headerParam = Arrays.stream(method.getParameters()) + .filter(p -> p.getName().equals("headerParam")) + .findFirst().get(); + + Boolean validationAnnotationExists = Arrays.stream(annotationsPerParameter) + .allMatch(annotations -> Arrays.stream(annotations) + .filter(a -> a.annotationType().equals(Valid.class)).toList() + .size() == 1); + + assertThat(validationAnnotationExists).isTrue(); + + assertThat(pathParam.isAnnotationPresent(Valid.class)).isTrue(); + assertThat(pathParam.isAnnotationPresent(NotNull.class)).isTrue(); + assertThat(pathParam.isAnnotationPresent(Pattern.class)).isTrue(); + assertThat(pathParam.isAnnotationPresent(Size.List.class)).isTrue(); + assertThat(pathParam.isAnnotationPresent(PathParam.class)).isTrue(); + + assertThat(headerParam.isAnnotationPresent(Valid.class)).isTrue(); + assertThat(headerParam.isAnnotationPresent(NotNull.class)).isFalse(); + assertThat(headerParam.isAnnotationPresent(Size.List.class)).isTrue(); + assertThat(headerParam.isAnnotationPresent(Pattern.class)).isTrue(); + assertThat(headerParam.isAnnotationPresent(HeaderParam.class)).isTrue(); + } }