From db5b6686ca7294548a84f3d1fd8e82a1f751c2fb Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Tue, 30 Apr 2019 07:28:21 -0400 Subject: [PATCH] Ignore hidden schema properties, add support for JSON-B name override * Add support for properties to be hidden using @Schema or @JsonbTransient * Add support for property key names to consider value of @JsonbProperty value attribute * Test cases --- implementation/pom.xml | 11 ++-- .../openapi/api/OpenApiConstants.java | 3 ++ .../dataobject/AnnotationTargetProcessor.java | 36 +++++++++---- .../scanner/dataobject/IgnoreResolver.java | 53 +++++++++++++++++++ .../runtime/scanner/ExpectationTests.java | 11 ++++ .../openapi/runtime/scanner/IgnoreTests.java | 29 ++++++++++ .../scanner/entities/FieldNameOverride.java | 46 ++++++++++++++++ .../entities/GenericTypeTestContainer.java | 3 ++ .../entities/IgnoreSchemaOnFieldExample.java | 21 ++++++++ .../JsonbTransientOnFieldExample.java | 15 ++++++ ...neric.fields.overriddenNames.expected.json | 31 +++++++++++ .../ignore.jsonbTransientField.expected.json | 13 +++++ .../ignore.schemaHiddenField.expected.json | 21 ++++++++ pom.xml | 6 +++ 14 files changed, 285 insertions(+), 14 deletions(-) create mode 100644 implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/FieldNameOverride.java create mode 100644 implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/IgnoreSchemaOnFieldExample.java create mode 100644 implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/JsonbTransientOnFieldExample.java create mode 100644 implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/generic.fields.overriddenNames.expected.json create mode 100644 implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.jsonbTransientField.expected.json create mode 100644 implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.schemaHiddenField.expected.json diff --git a/implementation/pom.xml b/implementation/pom.xml index 4d5d57c28..026996a45 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -35,13 +35,13 @@ org.eclipse.microprofile.openapi microprofile-openapi-api - + org.eclipse.microprofile.config microprofile-config-api - + com.fasterxml.jackson.core @@ -83,7 +83,7 @@ validation-api provided - + junit @@ -95,6 +95,11 @@ jsonassert test + + javax.json.bind + javax.json.bind-api + test + org.eclipse.microprofile.openapi microprofile-openapi-tck diff --git a/implementation/src/main/java/io/smallrye/openapi/api/OpenApiConstants.java b/implementation/src/main/java/io/smallrye/openapi/api/OpenApiConstants.java index 00d367bdf..9e58d0049 100644 --- a/implementation/src/main/java/io/smallrye/openapi/api/OpenApiConstants.java +++ b/implementation/src/main/java/io/smallrye/openapi/api/OpenApiConstants.java @@ -249,6 +249,9 @@ public final class OpenApiConstants { public static final DotName COMPLETION_STAGE_NAME = DotName.createSimple(CompletionStage.class.getName()); + public static final DotName DOTNAME_JSONB_PROPERTY = DotName.createSimple("javax.json.bind.annotation.JsonbProperty"); + public static final DotName DOTNAME_JSONB_TRANSIENT = DotName.createSimple("javax.json.bind.annotation.JsonbTransient"); + public static final String[] DEFAULT_CONSUMES = new String[] {MIME_ANY}; public static final String[] DEFAULT_PRODUCES = new String[] {MIME_ANY}; diff --git a/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/AnnotationTargetProcessor.java b/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/AnnotationTargetProcessor.java index 37b068c34..e78dc7b73 100644 --- a/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/AnnotationTargetProcessor.java +++ b/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/AnnotationTargetProcessor.java @@ -105,34 +105,48 @@ public static Schema process(AugmentedIndexView index, public Schema processField() { AnnotationInstance schemaAnnotation = TypeUtil.getSchemaAnnotation(annotationTarget); + final String propertyKey = readPropertyKey(); + if (schemaAnnotation == null && shouldInferUnannotatedFields()) { // Handle unannotated field and just do simple inference. readUnannotatedField(); } else { // Handle field annotated with @Schema. - readSchemaAnnotatedField(schemaAnnotation); + readSchemaAnnotatedField(propertyKey, schemaAnnotation); } - parentPathEntry.getSchema().addProperty(entityName, fieldSchema); + parentPathEntry.getSchema().addProperty(propertyKey, fieldSchema); return fieldSchema; } - private void readSchemaAnnotatedField(@NotNull AnnotationInstance annotation) { - if (annotation == null) { - throw new IllegalArgumentException("Annotation must not be null"); + private String readPropertyKey() { + AnnotationInstance jsonbAnnotation = TypeUtil.getAnnotation(annotationTarget, + OpenApiConstants.DOTNAME_JSONB_PROPERTY); + String key; + + if (jsonbAnnotation != null) { + key = JandexUtil.stringValue(jsonbAnnotation, OpenApiConstants.PROP_VALUE); + + if (key == null) { + key = entityName; + } + } else { + key = entityName; } - LOG.debugv("Processing @Schema annotation {0} on a field {1}", annotation, entityName); + return key; + } - // Schemas can be hidden. Skip if that's the case. - Boolean isHidden = JandexUtil.booleanValue(annotation, OpenApiConstants.PROP_HIDDEN); - if (isHidden != null && isHidden == Boolean.TRUE) { - return; + private void readSchemaAnnotatedField(String propertyKey, @NotNull AnnotationInstance annotation) { + if (annotation == null) { + throw new IllegalArgumentException("Annotation must not be null"); } + LOG.debugv("Processing @Schema annotation {0} on a field {1}", annotation, propertyKey); + // If "required" attribute is on field. It should be applied to the *parent* schema. // Required is false by default. if (JandexUtil.booleanValueWithDefault(annotation, OpenApiConstants.PROP_REQUIRED)) { - parentPathEntry.getSchema().addRequired(entityName); + parentPathEntry.getSchema().addRequired(propertyKey); } // Type could be replaced (e.g. generics). diff --git a/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java b/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java index d14b8012b..8d2988ecc 100644 --- a/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java +++ b/implementation/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/IgnoreResolver.java @@ -18,7 +18,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreType; + +import io.smallrye.openapi.api.OpenApiConstants; +import io.smallrye.openapi.runtime.scanner.dataobject.DataObjectDeque.PathEntry; +import io.smallrye.openapi.runtime.util.JandexUtil; import io.smallrye.openapi.runtime.util.TypeUtil; +import org.eclipse.microprofile.openapi.annotations.media.Schema; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; @@ -46,6 +51,8 @@ public class IgnoreResolver { { IgnoreAnnotationHandler[] ignoreHandlers = { + new SchemaHiddenHandler(), + new JsonbTransientHandler(), new JsonIgnorePropertiesHandler(), new JsonIgnoreHandler(), new JsonIgnoreTypeHandler() @@ -70,6 +77,52 @@ public boolean isIgnore(AnnotationTarget annotationTarget, DataObjectDeque.PathE return false; } + /** + * Handler for OAS hidden @{@link Schema} + */ + private final class SchemaHiddenHandler implements IgnoreAnnotationHandler { + @Override + public boolean shouldIgnore(AnnotationTarget target, PathEntry parentPathEntry) { + AnnotationInstance annotationInstance = TypeUtil.getAnnotation(target, getName()); + + if (annotationInstance != null) { + Boolean isHidden = JandexUtil.booleanValue(annotationInstance, + OpenApiConstants.PROP_HIDDEN); + + if (isHidden != null) { + return isHidden; + } + } + + return false; + } + + @Override + public DotName getName() { + return OpenApiConstants.DOTNAME_SCHEMA; + } + } + + /** + * Handler for JSON-B's @{@link javax.json.bind.annotation.JsonbTransient} + */ + private final class JsonbTransientHandler implements IgnoreAnnotationHandler { + + @Override + public boolean shouldIgnore(AnnotationTarget target, DataObjectDeque.PathEntry parentPathEntry) { + AnnotationInstance annotationInstance = TypeUtil.getAnnotation(target, getName()); + if (annotationInstance != null) { + return true; + } + return false; + } + + @Override + public DotName getName() { + return OpenApiConstants.DOTNAME_JSONB_TRANSIENT; + } + } + /** * Handler for Jackson's {@link JsonIgnoreProperties} */ diff --git a/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/ExpectationTests.java b/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/ExpectationTests.java index eec2e3871..8f981156c 100644 --- a/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/ExpectationTests.java +++ b/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/ExpectationTests.java @@ -135,4 +135,15 @@ public void genericFieldTest() throws IOException, JSONException { assertJsonEquals(name, "generic.fields.expected.json", result); } + @Test + public void fieldNameOverrideTest() throws IOException, JSONException { + String name = GenericTypeTestContainer.class.getName(); + Type pType = getFieldFromKlazz(name, "overriddenNames").type(); + OpenApiDataObjectScanner scanner = new OpenApiDataObjectScanner(index, pType); + + Schema result = scanner.process(); + + printToConsole(name, result); + assertJsonEquals(name, "generic.fields.overriddenNames.expected.json", result); + } } diff --git a/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/IgnoreTests.java b/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/IgnoreTests.java index 7a41690b2..5720bd1b3 100644 --- a/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/IgnoreTests.java +++ b/implementation/src/test/java/io/smallrye/openapi/runtime/scanner/IgnoreTests.java @@ -22,9 +22,12 @@ import org.jboss.jandex.Type; import org.json.JSONException; import org.junit.Test; + +import test.io.smallrye.openapi.runtime.scanner.entities.IgnoreSchemaOnFieldExample; import test.io.smallrye.openapi.runtime.scanner.entities.IgnoreTestContainer; import test.io.smallrye.openapi.runtime.scanner.entities.JsonIgnoreOnFieldExample; import test.io.smallrye.openapi.runtime.scanner.entities.JsonIgnoreTypeExample; +import test.io.smallrye.openapi.runtime.scanner.entities.JsonbTransientOnFieldExample; import java.io.IOException; @@ -85,4 +88,30 @@ public void testIgnore_jsonIgnoreType() throws IOException, JSONException { assertJsonEquals(name.local(), "ignore.jsonIgnoreType.expected.json", result); } + // Entirely ignore a single field once using JSON-B. + @Test + public void testIgnore_jsonbTransientField() throws IOException, JSONException { + DotName name = DotName.createSimple(JsonbTransientOnFieldExample.class.getName()); + OpenApiDataObjectScanner scanner = new OpenApiDataObjectScanner(index, + ClassType.create(name, Type.Kind.CLASS)); + + Schema result = scanner.process(); + + printToConsole(name.local(), result); + assertJsonEquals(name.local(), "ignore.jsonbTransientField.expected.json", result); + } + + // Entirely ignore a single field once using hidden attribute of Schema. + @Test + public void testIgnore_schemaHiddenField() throws IOException, JSONException { + DotName name = DotName.createSimple(IgnoreSchemaOnFieldExample.class.getName()); + OpenApiDataObjectScanner scanner = new OpenApiDataObjectScanner(index, + ClassType.create(name, Type.Kind.CLASS)); + + Schema result = scanner.process(); + + printToConsole(name.local(), result); + assertJsonEquals(name.local(), "ignore.schemaHiddenField.expected.json", result); + } + } diff --git a/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/FieldNameOverride.java b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/FieldNameOverride.java new file mode 100644 index 000000000..785b4eb3e --- /dev/null +++ b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/FieldNameOverride.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Red Hat, Inc, and individual contributors. + * + * 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 test.io.smallrye.openapi.runtime.scanner.entities; + +import javax.json.bind.annotation.JsonbProperty; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +/** + * @author Michael Edgar {@literal } + */ +public class FieldNameOverride { + + @JsonbProperty("dasherized-name") + private String dasherizedName; + + @JsonbProperty("dasherized-name-required") + @Schema(required = true) + private String dasherizedNameRequired; + + @JsonbProperty("snake_case_name") + private String snakeCaseName; + + @JsonbProperty("camelCaseNameCustom") + private String camelCaseName; + + @JsonbProperty + private String camelCaseNameDefault1; + + @SuppressWarnings("unused") + private String camelCaseNameDefault2; + +} diff --git a/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/GenericTypeTestContainer.java b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/GenericTypeTestContainer.java index 2abf5baad..071c2934a 100644 --- a/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/GenericTypeTestContainer.java +++ b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/GenericTypeTestContainer.java @@ -36,4 +36,7 @@ public class GenericTypeTestContainer { // Type containing a variety of collections and maps. GenericFieldTestContainer genericContainer; + + // Type containing fields with overridden names. + FieldNameOverride overriddenNames; } diff --git a/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/IgnoreSchemaOnFieldExample.java b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/IgnoreSchemaOnFieldExample.java new file mode 100644 index 000000000..b76b70098 --- /dev/null +++ b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/IgnoreSchemaOnFieldExample.java @@ -0,0 +1,21 @@ +package test.io.smallrye.openapi.runtime.scanner.entities; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +/** + * @author Michael Edgar {@literal } + */ + +public class IgnoreSchemaOnFieldExample { + @Schema(hidden = true) + String ignoredField; + + @Schema(hidden = false, description = "This field is not hidden") + String serializedField1; + + @Schema(description = "This field is not hidden either") + String serializedField2; + + String serializedField3; + +} diff --git a/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/JsonbTransientOnFieldExample.java b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/JsonbTransientOnFieldExample.java new file mode 100644 index 000000000..4e03ff4cc --- /dev/null +++ b/implementation/src/test/java/test/io/smallrye/openapi/runtime/scanner/entities/JsonbTransientOnFieldExample.java @@ -0,0 +1,15 @@ +package test.io.smallrye.openapi.runtime.scanner.entities; + +import javax.json.bind.annotation.JsonbTransient; + +/** + * @author Michael Edgar {@literal } + */ + +public class JsonbTransientOnFieldExample { + + @JsonbTransient + String ignoredField; + + String serializedField; +} diff --git a/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/generic.fields.overriddenNames.expected.json b/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/generic.fields.overriddenNames.expected.json new file mode 100644 index 000000000..9fb91d01a --- /dev/null +++ b/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/generic.fields.overriddenNames.expected.json @@ -0,0 +1,31 @@ +{ + "components" : { + "schemas" : { + "test.io.smallrye.openapi.runtime.scanner.entities.GenericTypeTestContainer" : { + "required" : [ + "dasherized-name-required" + ], + "properties" : { + "dasherized-name" : { + "type" : "string" + }, + "dasherized-name-required" : { + "type" : "string" + }, + "snake_case_name" : { + "type" : "string" + }, + "camelCaseNameCustom" : { + "type" : "string" + }, + "camelCaseNameDefault1" : { + "type" : "string" + }, + "camelCaseNameDefault2" : { + "type" : "string" + } + } + } + } + } +} diff --git a/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.jsonbTransientField.expected.json b/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.jsonbTransientField.expected.json new file mode 100644 index 000000000..44a5804f0 --- /dev/null +++ b/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.jsonbTransientField.expected.json @@ -0,0 +1,13 @@ +{ + "components" : { + "schemas" : { + "test.io.smallrye.openapi.runtime.scanner.entities.JsonbTransientOnFieldExample" : { + "properties" : { + "serializedField" : { + "type" : "string" + } + } + } + } + } +} diff --git a/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.schemaHiddenField.expected.json b/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.schemaHiddenField.expected.json new file mode 100644 index 000000000..0515e0d3d --- /dev/null +++ b/implementation/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.schemaHiddenField.expected.json @@ -0,0 +1,21 @@ +{ + "components" : { + "schemas" : { + "test.io.smallrye.openapi.runtime.scanner.entities.IgnoreSchemaOnFieldExample" : { + "properties" : { + "serializedField1" : { + "type" : "string", + "description" : "This field is not hidden" + }, + "serializedField2" : { + "type" : "string", + "description" : "This field is not hidden either" + }, + "serializedField3" : { + "type" : "string" + } + } + } + } + } +} diff --git a/pom.xml b/pom.xml index 44373f674..9ca3350f6 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ 7.0 2.0 2.0.1.Final + 1.0 4.12 1.3 2.0.0.0 @@ -128,6 +129,11 @@ validation-api ${version.javax.validation} + + javax.json.bind + javax.json.bind-api + ${version.javax.json.bind-api} +