GERONIMO-6676 GERONIMO-6677 enhancing our schema mapping to support a wider variety...
authorRomain Manni-Bucau <rmannibucau@gmail.com>
Wed, 9 Jan 2019 10:57:45 +0000 (11:57 +0100)
committerRomain Manni-Bucau <rmannibucau@gmail.com>
Wed, 9 Jan 2019 10:57:45 +0000 (11:57 +0100)
geronimo-openapi-impl/pom.xml
geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/AnnotationProcessor.java
geronimo-openapi-impl/src/main/java/org/apache/geronimo/microprofile/openapi/impl/processor/SchemaProcessor.java
geronimo-openapi-impl/src/test/java/org/apache/geronimo/microprofile/openapi/impl/processor/SchemaProcessorTest.java [new file with mode: 0644]
pom.xml

index af8dae2..1b95f16 100644 (file)
       <artifactId>meecrowave-arquillian</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.apache.openwebbeans</groupId>
-      <artifactId>openwebbeans-impl</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.eclipse.microprofile.openapi</groupId>
       <artifactId>microprofile-openapi-tck</artifactId>
     </dependency>
index 8123e4b..00075df 100644 (file)
@@ -306,33 +306,48 @@ public class AnnotationProcessor {
             final APIResponses responses = new APIResponsesImpl();
             responses.putAll(Stream.of(items).collect(toMap(it -> of(it.responseCode()).filter(c -> !c.isEmpty()).orElse("200"),
                     it -> mapResponse(api.getComponents(), it, produces.orElse(null)), (a, b) -> b)));
-            responses.values().stream().filter(it -> it.getContent() == null).forEach(v -> {
-                Type returnType = m.getReturnType();
-                if (returnType == void.class || returnType == Response.class) {
-                    final ContentImpl content = new ContentImpl();
-                    if (Response.class == returnType || Stream.of(m.getParameters()).anyMatch(it -> it.isAnnotationPresent(Suspended.class))) {
-                        content.put("200", new MediaTypeImpl());
-                    } else {
-                        content.put("204", new MediaTypeImpl());
-                    }
-                    v.content(content);
-                    return;
-                }
-
-                if (ParameterizedType.class.isInstance(returnType)) {
-                    final ParameterizedType pt = ParameterizedType.class.cast(returnType);
-                    if (pt.getActualTypeArguments().length > 0) {
-                        if (pt.getRawType() == CompletionStage.class) {
-                            returnType = pt.getActualTypeArguments()[0];
-                        }
-                    }
-                }
-                final ContentImpl content = new ContentImpl();
-                final MediaTypeImpl mediaType = new MediaTypeImpl();
-                mediaType.setSchema(schemaProcessor.mapSchemaFromClass(api.getComponents(), returnType));
-                content.put("", mediaType);
-                v.content(content);
-            });
+            responses.values().stream()
+                     .filter(it -> it.getContent() == null || it.getContent().isEmpty() ||
+                             it.getContent().values().stream().anyMatch(c -> c.getSchema() == null))
+                     .forEach(v -> {
+                         Type returnType = m.getReturnType();
+                         if (returnType == void.class || returnType == Response.class) {
+                             if (v.getContent() == null || v.getContent().isEmpty()) {
+                                 final ContentImpl content = new ContentImpl();
+                                 if (Response.class == returnType ||
+                                         Stream.of(m.getParameters()).anyMatch(it -> it.isAnnotationPresent(Suspended.class))) {
+                                     content.put("200", new MediaTypeImpl());
+                                 } else {
+                                     content.put("204", new MediaTypeImpl());
+                                 }
+                                 v.content(content);
+                             }
+                             return;
+                         }
+
+                         if (ParameterizedType.class.isInstance(returnType)) {
+                             final ParameterizedType pt = ParameterizedType.class.cast(returnType);
+                             if (pt.getActualTypeArguments().length > 0) {
+                                 if (pt.getRawType() == CompletionStage.class) {
+                                     returnType = pt.getActualTypeArguments()[0];
+                                 }
+                             }
+                         }
+                         final org.eclipse.microprofile.openapi.models.media.Schema schema =
+                                 schemaProcessor.mapSchemaFromClass(
+                                         api.getComponents(), returnType);
+                         if (v.getContent() == null || v.getContent().isEmpty()) {
+                             final ContentImpl content = new ContentImpl();
+                             final MediaTypeImpl mediaType = new MediaTypeImpl();
+                             mediaType.setSchema(schema);
+                             content.put("", mediaType);
+                             v.content(content);
+                         } else {
+                             v.getContent().values().stream()
+                                .filter(it -> it.getSchema() == null)
+                                .forEach(it -> it.schema(schema));
+                         }
+                    });
             responses.values().stream().filter(r -> r.getContent() != null)
                      .forEach(resp -> ofNullable(resp.getContent().remove(""))
                              .ifPresent(updated -> produces.ifPresent(mt -> mt.forEach(type -> resp.getContent().put(type, updated)))));
index a756250..1e5a295 100644 (file)
@@ -26,11 +26,15 @@ import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Stream;
 
+import javax.json.bind.annotation.JsonbProperty;
+import javax.ws.rs.core.Response;
+
 import org.apache.geronimo.microprofile.openapi.impl.model.DiscriminatorImpl;
 import org.apache.geronimo.microprofile.openapi.impl.model.SchemaImpl;
 import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
@@ -61,22 +65,35 @@ public class SchemaProcessor {
         if (Class.class.isInstance(model)) {
             if (boolean.class == model) {
                 schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.BOOLEAN);
+            } else if (Boolean.class == model) {
+                schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.BOOLEAN).nullable(true);
             } else if (String.class == model) {
                 schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.STRING);
             } else if (double.class == model || float.class == model) {
                 schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.NUMBER);
-            } else if (int.class == model || short.class == model || byte.class == model) {
+            } else if (Double.class == model || Float.class == model) {
+                schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.NUMBER).nullable(true);
+            } else if (int.class == model || short.class == model || byte.class == model || long.class == model) {
                 schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.INTEGER);
+            } else if (Integer.class == model || Short.class == model || Byte.class == model || Long.class == model) {
+                schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.INTEGER).nullable(true);
+            } else if (Response.class == model) {
+                schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.OBJECT).nullable(true);
+            } else if (isStringable(model)) {
+                schema.type(org.eclipse.microprofile.openapi.models.media.Schema.SchemaType.STRING).nullable(true);
             } else {
-                // todo cache schema in components and just set the ref here - jsonb mapping based
                 final Class from = Class.class.cast(model);
                 ofNullable(from.getAnnotation(Schema.class)).ifPresent(s -> sets(components, Schema.class.cast(s), schema));
-                schema.items(new SchemaImpl());
+                // schema.items(new SchemaImpl());
                 schema.properties(new HashMap<>());
                 Class<?> current = from;
+                // todo: use getters first then fields, for JSON-B the private only fields must be ignored
                 while (current != null && current != Object.class) {
                     Stream.of(current.getDeclaredFields())
-                            .filter(f -> f.isAnnotationPresent(Schema.class) && !f.getAnnotation(Schema.class).hidden())
+                            .filter(f -> {
+                                final boolean explicit = f.isAnnotationPresent(Schema.class);
+                                return !explicit || !f.getAnnotation(Schema.class).hidden();
+                            })
                             .peek(f -> handleRequired(schema, f))
                             .forEach(f -> {
                                 final String fieldName = findFieldName(f);
@@ -103,8 +120,12 @@ public class SchemaProcessor {
         }
     }
 
+    private boolean isStringable(final Type model) {
+        return Date.class == model || model.getTypeName().startsWith("java.time.");
+    }
+
     private void handleRequired(final org.eclipse.microprofile.openapi.models.media.Schema schema, final Field f) {
-        if (!f.getAnnotation(Schema.class).required()) {
+        if (!f.isAnnotationPresent(Schema.class) || !f.getAnnotation(Schema.class).required()) {
             return;
         }
         if (schema.getRequired() == null) {
@@ -117,16 +138,27 @@ public class SchemaProcessor {
     }
 
     private org.eclipse.microprofile.openapi.models.media.Schema mapField(final Components components, final Field f) {
-        return ofNullable(mapSchema(components, f.getAnnotation(Schema.class))).orElseGet(() -> {
+        final Schema annotation = f.getAnnotation(Schema.class);
+        return ofNullable(annotation).map(s -> mapSchema(components, s)).orElseGet(() -> {
             final org.eclipse.microprofile.openapi.models.media.Schema schemaFromClass = mapSchemaFromClass(
                     components, f.getGenericType());
-            mergeSchema(components, schemaFromClass, f.getAnnotation(Schema.class));
+            if (annotation != null) {
+                mergeSchema(components, schemaFromClass, annotation);
+            }
             return schemaFromClass;
         });
     }
 
     private String findFieldName(final Field f) {
-        return of(f.getAnnotation(Schema.class).name()).filter(it -> !it.isEmpty()).orElseGet(f::getName);
+        return ofNullable(f.getAnnotation(Schema.class))
+                .map(Schema::name)
+                .filter(it -> !it.isEmpty())
+                .orElseGet(() -> {
+                    if (f.isAnnotationPresent(JsonbProperty.class)) { // todo: test getter too
+                        return f.getAnnotation(JsonbProperty.class).value();
+                    }
+                    return f.getName();
+                });
     }
 
     private void mergeSchema(final Components components, final org.eclipse.microprofile.openapi.models.media.Schema impl,
@@ -234,7 +266,12 @@ public class SchemaProcessor {
             impl.ref(resolveSchemaRef(components, schema.ref()));
         } else {
             if (schema.implementation() != Void.class) {
-                fillSchema(components, schema.implementation(), impl);
+                final boolean array = schema.type() == SchemaType.ARRAY;
+                final org.eclipse.microprofile.openapi.models.media.Schema ref = array ? new SchemaImpl() : impl;
+                fillSchema(components, schema.implementation(), ref);
+                if (array) {
+                    impl.items(ref);
+                }
             }
             mergeSchema(components, impl, schema);
         }
diff --git a/geronimo-openapi-impl/src/test/java/org/apache/geronimo/microprofile/openapi/impl/processor/SchemaProcessorTest.java b/geronimo-openapi-impl/src/test/java/org/apache/geronimo/microprofile/openapi/impl/processor/SchemaProcessorTest.java
new file mode 100644 (file)
index 0000000..e6b617c
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.geronimo.microprofile.openapi.impl.processor;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.json.bind.annotation.JsonbProperty;
+
+import org.apache.geronimo.microprofile.openapi.impl.model.ComponentsImpl;
+import org.eclipse.microprofile.openapi.models.media.Schema;
+import org.testng.annotations.Test;
+
+public class SchemaProcessorTest {
+    @Test
+    public void mapImplicit() {
+        final Schema schema = new SchemaProcessor().mapSchemaFromClass(new ComponentsImpl(), Data.class);
+        assertEquals(1, schema.getProperties().size());
+        assertEquals(Schema.SchemaType.STRING, schema.getProperties().get("name").getType());
+    }
+
+    @Test
+    public void mapJsonb() {
+        final Schema schema = new SchemaProcessor().mapSchemaFromClass(new ComponentsImpl(), JsonbData.class);
+        assertEquals(1, schema.getProperties().size());
+        assertEquals(Schema.SchemaType.STRING, schema.getProperties().get("foo").getType());
+    }
+
+    public static class Data {
+        private String name;
+    }
+
+    public static class JsonbData {
+        @JsonbProperty("foo")
+        private String name;
+    }
+}
diff --git a/pom.xml b/pom.xml
index 43b4a4e..e0d1b20 100644 (file)
--- a/pom.xml
+++ b/pom.xml
       <dependency>
         <groupId>org.apache.meecrowave</groupId>
         <artifactId>meecrowave-arquillian</artifactId>
-        <version>1.2.3</version>
+        <version>1.2.4</version>
         <scope>test</scope>
         <exclusions>
           <exclusion>
         </exclusions>
       </dependency>
       <dependency>
-        <groupId>org.apache.openwebbeans</groupId>
-        <artifactId>openwebbeans-impl</artifactId>
-        <version>2.0.6</version>
-        <scope>test</scope>
-      </dependency>
-      <dependency>
         <groupId>org.eclipse.microprofile.openapi</groupId>
         <artifactId>microprofile-openapi-tck</artifactId>
         <version>${spec.version}</version>