diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/OASConfig.java b/api/src/main/java/org/eclipse/microprofile/openapi/OASConfig.java index 5a1504077..5098b0a2f 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/OASConfig.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/OASConfig.java @@ -67,6 +67,11 @@ private OASConfig() { */ public static final String SCAN_EXCLUDE_CLASSES = "mp.openapi.scan.exclude.classes"; + /** + * Configuration property to enable or disable scanning Jakarta Bean Validation annotations + */ + public static final String SCAN_BEANVALIDATION = "mp.openapi.scan.beanvalidation"; + /** * Configuration property to specify the list of global servers that provide connectivity information. * diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java index 3a56ebd10..166f3eebb 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/OpenAPIDefinition.java @@ -69,8 +69,8 @@ /** * A declaration of which security mechanisms can be used across the API. *
- * Adding a {@code SecurityRequirement} to this array is equivalent to adding a {@code SecurityRequirementsSet} containing - * a single {@code SecurityRequirement} to {@link #securitySets()}. + * Adding a {@code SecurityRequirement} to this array is equivalent to adding a {@code SecurityRequirementsSet} + * containing a single {@code SecurityRequirement} to {@link #securitySets()}. * * @return the array of security requirements for this API */ diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirements.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirements.java index c69a59e17..7985f3cbb 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirements.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirements.java @@ -25,8 +25,8 @@ /** * Container annotation for repeated {@link SecurityRequirement} annotations. *
- * Note that each {@code SecurityRequirement} annotation is equivalent to a {@link SecurityRequirementsSet} annotation containing only that - * annotation. + * Note that each {@code SecurityRequirement} annotation is equivalent to a {@link SecurityRequirementsSet} + * annotation containing only that annotation. * *
* Example: diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSet.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSet.java index 4cf5dab1e..af8d20f00 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSet.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSet.java @@ -26,18 +26,21 @@ import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition; /** - * This annotation represents a set of security requirements which permit access to an operation if all of them are satisfied. + * This annotation represents a set of security requirements which permit access to an operation if all of them are + * satisfied. ** * @see SecurityRequirement - * Object + * "https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#security-requirement-object">SecurityRequirement + * Object **/ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSets.java b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSets.java index e2ff2b120..2504fe16c 100644 --- a/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSets.java +++ b/api/src/main/java/org/eclipse/microprofile/openapi/annotations/security/SecurityRequirementsSets.java @@ -24,18 +24,23 @@ import java.lang.annotation.Target; /** - * Represents an array of security requirement sets that apply to an operation. Only one of requirement sets needs be satisfied to access the - * operation. + * Represents an array of security requirement sets that apply to an operation. Only one of requirement sets needs be + * satisfied to access the operation. *- * If this annotation is applied to a method which corresponds to an operation, then the requirements will be added to that operation. + * If this annotation is applied to a method which corresponds to an operation, then the requirements will be added to + * that operation. *
- * If this annotation is applied to a class which contains methods which correspond to operations, then the requirements will be added to all - * operations corresponding to methods within that class which don't specify any other requirements. + * If this annotation is applied to a class which contains methods which correspond to operations, then the requirements + * will be added to all operations corresponding to methods within that class which don't specify any other + * requirements. *
- * Security requirements can be specified for the whole API using {@link OpenAPIDefinition#securitySets()}. Security requirements specified - * for individual operations override those specified for the whole API. + * Security requirements can be specified for the whole API using {@link OpenAPIDefinition#securitySets()}. Security + * requirements specified for individual operations override those specified for the whole API. *
- * If multiple security requirement sets are specified for an operation, then a user must satisfy all of the requirements within any one of the sets - * to access the operation. + * If multiple security requirement sets are specified for an operation, then a user must satisfy all of the + * requirements within any one of the sets to access the operation. *
* An empty security requirement set indicates that authentication is not required. *
@@ -51,8 +54,8 @@ *
- * If this annotation is applied to a method which corresponds to an operation, then the requirements will be added to that operation. + * If this annotation is applied to a method which corresponds to an operation, then the requirements will be added to + * that operation. *
- * If this annotation is applied to a class which contains methods which correspond to operations, then the requirements will be added to all - * operations corresponding to methods within that class which don't specify any other requirements. + * If this annotation is applied to a class which contains methods which correspond to operations, then the requirements + * will be added to all operations corresponding to methods within that class which don't specify any other + * requirements. *
- * This annotation may be used with {@code value} set to an empty array. When applied like this to a method or class, it indicates that no security - * requirements apply to the corresponding operations. This can be used to override security requirements which are specified for the whole API. + * This annotation may be used with {@code value} set to an empty array. When applied like this to a method or class, it + * indicates that no security requirements apply to the corresponding operations. This can be used to override security + * requirements which are specified for the whole API. *
- * A {@code SecurityRequirementSets} annotation corresponds to an array of maps of security requirements in an OpenAPI document. + * A {@code SecurityRequirementSets} annotation corresponds to an array of maps of security requirements in an OpenAPI + * document. + * *
* Example:
* security:
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/package-info.java
index bef2a1a1b..a745ada33 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/package-info.java
@@ -16,6 +16,6 @@
* v3 documents from their JAX-RS applications.
*/
-@org.osgi.annotation.versioning.Version("2.0")
+@org.osgi.annotation.versioning.Version("2.1")
@org.osgi.annotation.versioning.ProviderType
package org.eclipse.microprofile.openapi;
\ No newline at end of file
diff --git a/spec/src/main/asciidoc/microprofile-openapi-spec.asciidoc b/spec/src/main/asciidoc/microprofile-openapi-spec.asciidoc
index f3e740186..f4577cd13 100644
--- a/spec/src/main/asciidoc/microprofile-openapi-spec.asciidoc
+++ b/spec/src/main/asciidoc/microprofile-openapi-spec.asciidoc
@@ -119,6 +119,10 @@ Configuration property to specify the list of packages to exclude from scans. Fo
Configuration property to specify the list of classes to exclude from scans. For example,
`mp.openapi.scan.exclude.classes=com.xyz.MyClassC,com.xyz.MyClassD`
+[#scan-beanvalidation-config]
+`mp.openapi.scan.beanvalidation`::
+Configuration property to enable or disable the scanning and processing of Jakarta Bean Validation annotations. Defaults to `true`.
+
`mp.openapi.servers`::
Configuration property to specify the list of global servers that provide connectivity information. For example,
`mp.openapi.servers=https://xyz.com/v1,https://abc.com/v1`
@@ -573,6 +577,48 @@ post:
For more samples please see the https://github.com/eclipse/microprofile-open-api/wiki[MicroProfile Wiki].
+==== Jakarta Bean Validation Annotations
+
+In some cases, additional schema restrictions can be inferred from Jakarta Bean Validation annotations and used to enhance the generated OpenAPI document.
+
+Implementations are required to process Jakarta Bean Validation annotations and add the properties listed in the table below to the schema model when:
+* the annotation is applied to to an element for which a schema is generated and
+* the annotation and generated schema type are listed together in the table below and
+* the annotation has a `group` attribute which is empty or includes `jakarta.validation.groups.Default` and
+* the user has not set any of the relevant property values using other annotations and
+* processing of bean validation annotations has not been disabled <>
+
+|===
+| Annotation | Schema type | Schema properties to set
+| `@NotEmpty` | `string` | `minLength = 1`
+| `@NotEmpty` | `array` | `minItems = 1`
+| `@NotEmpty` | `object` | `minProperties = 1`
+| `@NotBlank` | `string` | `pattern = \S`
+| `@Size(min = a, max = b)` | `string`
+| `minLength = a` +
+`maxLenth = b`
+| `@Size(min = a, max = b)` | `array`
+| `minItems = a` +
+`maxItems = b`
+| `@Size(min = a, max = b)` | `object`
+| `minProperties = a` +
+`maxProperties = b`
+| `@DecimalMax(value = a)` | `number` or `integer` | `maximum = a`
+| `@DecimalMax(value = a, exclusive = false)` | `number` or `integer` | `maximum = a` +
+`exclusiveMaximum = true`
+| `@DecimalMin(value = a)` | `number` or `integer` | `minimum = a`
+| `@DecimalMin(value = a, exclusive = false)` | `number` or `integer` | `minimum = a` +
+`exclusiveMinimum = true`
+| `@Max(a)` | `number` or `integer` | `maximum = a`
+| `@Min(a)` | `number` or `integer` | `minimum = a`
+| `@Negative` | `number` or `integer` | `maximum = 0` +
+`exclusiveMaximum = true`
+| `@NegativeOrZero` | `number` or `integer` | `maximum = 0`
+| `@Positive` | `number` or `integer` | `minimum = 0` +
+`exclusiveMinimum = true`
+| `@PositiveOrZero` | `number` or `integer` | `minimum = 0`
+|===
+
=== Static OpenAPI files
Application developers may wish to include a pre-generated OpenAPI document that
diff --git a/tck/pom.xml b/tck/pom.xml
index c3b48434e..cd3a2df8f 100644
--- a/tck/pom.xml
+++ b/tck/pom.xml
@@ -49,6 +49,12 @@
3.0.0
provided
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.0.0
+ provided
+
org.testng
diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationApp.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationApp.java
new file mode 100644
index 000000000..ab183d4db
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationApp.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.eclipse.microprofile.openapi.apps.beanvalidation;
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
+
+@ApplicationPath("/")
+public class BeanValidationApp extends Application {
+
+}
diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationData.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationData.java
new file mode 100644
index 000000000..e5fa143e8
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationData.java
@@ -0,0 +1,259 @@
+/**
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.eclipse.microprofile.openapi.apps.beanvalidation;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.Negative;
+import jakarta.validation.constraints.NegativeOrZero;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
+import jakarta.validation.groups.Default;
+
+public class BeanValidationData {
+
+ @NotEmpty
+ private String notEmptyString;
+
+ @NotEmpty
+ private List notEmptyList;
+
+ @NotEmpty
+ private Map notEmptyMap;
+
+ @NotBlank
+ private String notBlankString;
+
+ @Size(min = 2, max = 7)
+ private String sizedString;
+
+ @Size(min = 1, max = 10)
+ private List sizedList;
+
+ @Size(min = 3, max = 5)
+ private Map sizedMap;
+
+ @DecimalMax("1.5")
+ private BigDecimal maxDecimalInclusive;
+
+ @DecimalMax(value = "1.5", inclusive = false)
+ private BigDecimal maxDecimalExclusive;
+
+ @DecimalMin("3.25")
+ private BigDecimal minDecimalInclusive;
+
+ @DecimalMin(value = "3.25", inclusive = false)
+ private BigDecimal minDecimalExclusive;
+
+ @Max(5)
+ private int maxInt;
+
+ @Min(7)
+ private int minInt;
+
+ @Negative
+ private int negativeInt;
+
+ @NegativeOrZero
+ private int negativeOrZeroInt;
+
+ @Positive
+ private int positiveInt;
+
+ @PositiveOrZero
+ private int positiveOrZeroInt;
+
+ @Schema(minLength = 6)
+ @NotEmpty
+ private String overridenBySchemaAnnotation;
+
+ @NotEmpty(groups = TestGroup.class)
+ private String nonDefaultGroup;
+
+ @NotEmpty(groups = {Default.class, TestGroup.class})
+ private String defaultAndOtherGroups;
+
+ public String getNotEmptyString() {
+ return notEmptyString;
+ }
+
+ public void setNotEmptyString(String notEmptyString) {
+ this.notEmptyString = notEmptyString;
+ }
+
+ public List getNotEmptyList() {
+ return notEmptyList;
+ }
+
+ public void setNotEmptyList(List notEmptyList) {
+ this.notEmptyList = notEmptyList;
+ }
+
+ public Map getNotEmptyMap() {
+ return notEmptyMap;
+ }
+
+ public void setNotEmptyMap(Map notEmptyMap) {
+ this.notEmptyMap = notEmptyMap;
+ }
+
+ public String getNotBlankString() {
+ return notBlankString;
+ }
+
+ public void setNotBlankString(String notBlankString) {
+ this.notBlankString = notBlankString;
+ }
+
+ public String getSizedString() {
+ return sizedString;
+ }
+
+ public void setSizedString(String sizedString) {
+ this.sizedString = sizedString;
+ }
+
+ public List getSizedList() {
+ return sizedList;
+ }
+
+ public void setSizedList(List sizedList) {
+ this.sizedList = sizedList;
+ }
+
+ public Map getSizedMap() {
+ return sizedMap;
+ }
+
+ public void setSizedMap(Map sizedMap) {
+ this.sizedMap = sizedMap;
+ }
+
+ public BigDecimal getMaxDecimalInclusive() {
+ return maxDecimalInclusive;
+ }
+
+ public void setMaxDecimalInclusive(BigDecimal maxDecimalInclusive) {
+ this.maxDecimalInclusive = maxDecimalInclusive;
+ }
+
+ public BigDecimal getMaxDecimalExclusive() {
+ return maxDecimalExclusive;
+ }
+
+ public void setMaxDecimalExclusive(BigDecimal maxDecimalExclusive) {
+ this.maxDecimalExclusive = maxDecimalExclusive;
+ }
+
+ public BigDecimal getMinDecimalInclusive() {
+ return minDecimalInclusive;
+ }
+
+ public void setMinDecimalInclusive(BigDecimal minDecimalInclusive) {
+ this.minDecimalInclusive = minDecimalInclusive;
+ }
+
+ public BigDecimal getMinDecimalExclusive() {
+ return minDecimalExclusive;
+ }
+
+ public void setMinDecimalExclusive(BigDecimal minDecimalExclusive) {
+ this.minDecimalExclusive = minDecimalExclusive;
+ }
+
+ public int getMaxInt() {
+ return maxInt;
+ }
+
+ public void setMaxInt(int maxInt) {
+ this.maxInt = maxInt;
+ }
+
+ public int getMinInt() {
+ return minInt;
+ }
+
+ public void setMinInt(int minInt) {
+ this.minInt = minInt;
+ }
+
+ public int getNegativeInt() {
+ return negativeInt;
+ }
+
+ public void setNegativeInt(int negativeInt) {
+ this.negativeInt = negativeInt;
+ }
+
+ public int getNegativeOrZeroInt() {
+ return negativeOrZeroInt;
+ }
+
+ public void setNegativeOrZeroInt(int negativeOrZeroInt) {
+ this.negativeOrZeroInt = negativeOrZeroInt;
+ }
+
+ public int getPositiveInt() {
+ return positiveInt;
+ }
+
+ public void setPositiveInt(int positiveInt) {
+ this.positiveInt = positiveInt;
+ }
+
+ public int getPositiveOrZeroInt() {
+ return positiveOrZeroInt;
+ }
+
+ public void setPositiveOrZeroInt(int positiveOrZeroInt) {
+ this.positiveOrZeroInt = positiveOrZeroInt;
+ }
+
+ public String getOverridenBySchemaAnnotation() {
+ return overridenBySchemaAnnotation;
+ }
+
+ public void setOverridenBySchemaAnnotation(String overridenBySchemaAnnotation) {
+ this.overridenBySchemaAnnotation = overridenBySchemaAnnotation;
+ }
+
+ public String getNonDefaultGroup() {
+ return nonDefaultGroup;
+ }
+
+ public void setNonDefaultGroup(String nonDefaultGroup) {
+ this.nonDefaultGroup = nonDefaultGroup;
+ }
+
+ public String getDefaultAndOtherGroups() {
+ return defaultAndOtherGroups;
+ }
+
+ public void setDefaultAndOtherGroups(String defaultAndOtherGroups) {
+ this.defaultAndOtherGroups = defaultAndOtherGroups;
+ }
+}
diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationResource.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationResource.java
new file mode 100644
index 000000000..8d6a5b8d2
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/BeanValidationResource.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.eclipse.microprofile.openapi.apps.beanvalidation;
+
+import jakarta.validation.Valid;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/")
+public class BeanValidationResource {
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void test(@Valid BeanValidationData data) {
+ }
+}
diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/TestGroup.java b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/TestGroup.java
new file mode 100644
index 000000000..06a890502
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/openapi/apps/beanvalidation/TestGroup.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.eclipse.microprofile.openapi.apps.beanvalidation;
+
+/**
+ * A non-default bean validation constraint group for the bean validation test
+ */
+public interface TestGroup {
+}
diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationDisabledTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationDisabledTest.java
new file mode 100644
index 000000000..acc8e5c3c
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationDisabledTest.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.eclipse.microprofile.openapi.tck.beanvalidation;
+
+import static org.eclipse.microprofile.openapi.tck.beanvalidation.BeanValidationTest.assertProperty;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.not;
+
+import org.eclipse.microprofile.openapi.OASConfig;
+import org.eclipse.microprofile.openapi.apps.beanvalidation.BeanValidationApp;
+import org.eclipse.microprofile.openapi.tck.AppTestBase;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.Asset;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.testng.annotations.Test;
+
+import io.restassured.response.ValidatableResponse;
+
+public class BeanValidationDisabledTest extends AppTestBase {
+
+ @Deployment
+ public static WebArchive buildApp() {
+ Asset config = new StringAsset(OASConfig.SCAN_BEANVALIDATION + "=false");
+
+ return ShrinkWrap.create(WebArchive.class, "beanValidation.war")
+ .addPackage(BeanValidationApp.class.getPackage())
+ .addAsManifestResource(config, "microprofile-config.properties");
+ }
+
+ @Test(dataProvider = "formatProvider")
+ @RunAsClient
+ public void beanValidationScanningDisabledTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "notEmptyString", not(hasKey("minLength")));
+ }
+
+}
diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationTest.java
new file mode 100644
index 000000000..50a90de27
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/beanvalidation/BeanValidationTest.java
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.eclipse.microprofile.openapi.tck.beanvalidation;
+
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.not;
+
+import org.eclipse.microprofile.openapi.apps.beanvalidation.BeanValidationApp;
+import org.eclipse.microprofile.openapi.tck.AppTestBase;
+import org.hamcrest.Matcher;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.testng.annotations.Test;
+
+import io.restassured.response.ValidatableResponse;
+
+public class BeanValidationTest extends AppTestBase {
+
+ @Deployment
+ public static WebArchive buildApp() {
+ return ShrinkWrap.create(WebArchive.class, "beanValidation.war")
+ .addPackage(BeanValidationApp.class.getPackage());
+ }
+
+ @Test(dataProvider = "formatProvider")
+ @RunAsClient
+ public void notEmptyStringTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "notEmptyString", hasEntry("minLength", 1));
+ assertProperty(vr, "notEmptyString", hasEntry("type", "string"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ @RunAsClient
+ public void notEmptyListTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "notEmptyList", hasEntry("minItems", 1));
+ assertProperty(vr, "notEmptyList", hasEntry("type", "array"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ @RunAsClient
+ public void notEmptyMapTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "notEmptyMap", hasEntry("minProperties", 1));
+ assertProperty(vr, "notEmptyMap", hasEntry("type", "object"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ @RunAsClient
+ public void notBlankStringTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "notBlankString", hasEntry("pattern", "\\S"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ @RunAsClient
+ public void sizedStringTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "sizedString", hasEntry("minLength", 2));
+ assertProperty(vr, "sizedString", hasEntry("maxLength", 7));
+ assertProperty(vr, "sizedString", hasEntry("type", "string"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void sizedListTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "sizedList", hasEntry("minItems", 1));
+ assertProperty(vr, "sizedList", hasEntry("maxItems", 10));
+ assertProperty(vr, "sizedList", hasEntry("type", "array"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void sizedMapTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "sizedMap", hasEntry("minProperties", 3));
+ assertProperty(vr, "sizedMap", hasEntry("maxProperties", 5));
+ assertProperty(vr, "sizedMap", hasEntry("type", "object"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void maxDecimalInclusiveTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "maxDecimalInclusive", hasEntry("maximum", 1.5f));
+ assertProperty(vr, "maxDecimalInclusive", hasEntry("type", "number"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void maxDecimalExclusiveTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "maxDecimalExclusive", hasEntry("maximum", 1.5f));
+ assertProperty(vr, "maxDecimalExclusive", hasEntry("exclusiveMaximum", true));
+ assertProperty(vr, "maxDecimalExclusive", hasEntry("type", "number"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void minDecimalInclusiveTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "minDecimalInclusive", hasEntry("minimum", 3.25f));
+ assertProperty(vr, "minDecimalInclusive", hasEntry("type", "number"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void minDecimalExclusiveTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "minDecimalExclusive", hasEntry("minimum", 3.25f));
+ assertProperty(vr, "minDecimalExclusive", hasEntry("exclusiveMinimum", true));
+ assertProperty(vr, "minDecimalExclusive", hasEntry("type", "number"));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void maxIntTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "maxInt", hasEntry("maximum", 5));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void minIntTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "minInt", hasEntry("minimum", 7));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void negativeIntTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "negativeInt", hasEntry("maximum", 0));
+ assertProperty(vr, "negativeInt", hasEntry("exclusiveMaximum", true));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void negativeOrZeroIntTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "negativeOrZeroInt", hasEntry("maximum", 0));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void positiveIntTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "positiveInt", hasEntry("minimum", 0));
+ assertProperty(vr, "positiveInt", hasEntry("exclusiveMinimum", true));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void positiveOrZeroIntTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "positiveOrZeroInt", hasEntry("minimum", 0));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void overridenBySchemaAnnotationTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "overridenBySchemaAnnotation", hasEntry("minLength", 6));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void nonDefaultGroupTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "nonDefaultGroup", not(hasKey("minLength")));
+ }
+
+ @Test(dataProvider = "formatProvider")
+ public void defaultAndOtherGroupsTest(String format) {
+ ValidatableResponse vr = callEndpoint(format);
+ assertProperty(vr, "defaultAndOtherGroups", hasEntry("minLength", 1));
+ }
+
+ /**
+ * Asserts that a property from the test schema matches the given matcher
+ *
+ * @param vr
+ * the response
+ * @param propertyName
+ * the property to test
+ * @param matcher
+ * the matcher to assert
+ */
+ public static void assertProperty(ValidatableResponse vr, String propertyName, Matcher> matcher) {
+ String schemaPath = dereference(vr, "paths.'/'.post.requestBody", "content.'application/json'.schema");
+ String propertyPath = schemaPath + ".properties." + propertyName;
+ vr.body(propertyPath, matcher);
+ }
+
+}