From f5c243a6236e3c5a362818e7c2178d2306940a96 Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Wed, 18 Jan 2023 16:53:00 -0500 Subject: [PATCH] Represent Java BaseStream as array of T in generated schema Fixes #1359 Signed-off-by: Michael Edgar --- .../runtime/scanner/CollectionStandin.java | 10 --- .../scanner/OpenApiDataObjectScanner.java | 45 +++++----- .../runtime/scanner/StreamStandin.java | 7 ++ .../scanner/dataobject/TypeProcessor.java | 52 ++++++++---- .../scanner/dataobject/TypeResolver.java | 31 ++++--- .../spi/AbstractParameterProcessor.java | 2 +- .../scanner/spi/AnnotationScannerContext.java | 1 + .../openapi/runtime/util/TypeUtil.java | 9 +- .../scanner/OpenApiDataObjectScannerTest.java | 44 ++++++++++ .../scanner/StandaloneSchemaScanTest.java | 36 ++++++++ ...nts.schemas.iterator-stream-map-types.json | 60 +++++++++++++ .../JaxRsDataObjectScannerTestBase.java | 5 -- .../openapi/runtime/util/TypeUtilTest.java | 22 ++--- .../jakarta/AllTheParamsTestResource.java | 8 ++ ...tEasyReactiveAllTheParamsTestResource.java | 8 ++ .../javax/AllTheParamsTestResource.java | 8 ++ ...tEasyReactiveAllTheParamsTestResource.java | 8 ++ .../ignore.synthetic-classes-interfaces.json | 5 +- .../scanner/params.all-the-params.json | 84 +++++++++++++++++++ .../refsEnabled.kitchenSink.expected.json | 17 +++- .../responses.component-status-reuse.json | 3 +- .../io/smallrye/openapi/tck/ArchiveUtil.java | 14 ---- 22 files changed, 385 insertions(+), 94 deletions(-) delete mode 100644 core/src/main/java/io/smallrye/openapi/runtime/scanner/CollectionStandin.java create mode 100644 core/src/main/java/io/smallrye/openapi/runtime/scanner/StreamStandin.java create mode 100644 core/src/test/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScannerTest.java create mode 100644 core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.iterator-stream-map-types.json diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/CollectionStandin.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/CollectionStandin.java deleted file mode 100644 index ac7adfa7e..000000000 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/CollectionStandin.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.smallrye.openapi.runtime.scanner; - -import java.util.Collection; - -/** - * @author Marc Savy {@literal } - */ -public abstract class CollectionStandin implements Collection { - E value; -} diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScanner.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScanner.java index 0deca9b4d..013a90cea 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScanner.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScanner.java @@ -6,8 +6,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; -import java.util.Collection; import java.util.Map; +import java.util.stream.BaseStream; import org.eclipse.microprofile.openapi.models.media.Schema; import org.eclipse.microprofile.openapi.models.media.Schema.SchemaType; @@ -67,14 +67,14 @@ */ public class OpenApiDataObjectScanner { - // Collection (list-type things) - public static final DotName COLLECTION_INTERFACE_NAME = DotName.createSimple(Collection.class.getName()); - public static final Type COLLECTION_TYPE = Type.create(COLLECTION_INTERFACE_NAME, Type.Kind.CLASS); - // Iterable (also list-type things) public static final DotName ITERABLE_INTERFACE_NAME = DotName.createSimple(Iterable.class.getName()); public static final Type ITERABLE_TYPE = Type.create(ITERABLE_INTERFACE_NAME, Type.Kind.CLASS); + // Stream + public static final DotName STREAM_INTERFACE_NAME = DotName.createSimple(BaseStream.class.getName()); + public static final Type STREAM_TYPE = Type.create(STREAM_INTERFACE_NAME, Type.Kind.CLASS); + // Map public static final DotName MAP_INTERFACE_NAME = DotName.createSimple(Map.class.getName()); public static final Type MAP_TYPE = Type.create(MAP_INTERFACE_NAME, Type.Kind.CLASS); @@ -93,9 +93,9 @@ public class OpenApiDataObjectScanner { // Array type public static final Type ARRAY_TYPE_OBJECT = Type.create(DotName.createSimple(Map[].class.getName()), Type.Kind.ARRAY); - private static ClassInfo collectionStandin; private static ClassInfo iterableStandin; private static ClassInfo mapStandin; + private static ClassInfo streamStandin; /*- * Index the "standin" collection types for internal use. These are required to wrap @@ -103,13 +103,13 @@ public class OpenApiDataObjectScanner { */ static { Indexer indexer = new Indexer(); - index(indexer, "CollectionStandin.class"); index(indexer, "IterableStandin.class"); index(indexer, "MapStandin.class"); + index(indexer, "StreamStandin.class"); Index index = indexer.complete(); - collectionStandin = index.getClassByName(DotName.createSimple(CollectionStandin.class.getName())); iterableStandin = index.getClassByName(DotName.createSimple(IterableStandin.class.getName())); mapStandin = index.getClassByName(DotName.createSimple(MapStandin.class.getName())); + streamStandin = index.getClassByName(DotName.createSimple(StreamStandin.class.getName())); } private static void index(Indexer indexer, String resourceName) { @@ -209,7 +209,7 @@ Schema process() { // For certain special types (map, list, etc) we need to do some pre-processing. if (isSpecialType(rootClassType)) { - resolveSpecial(root, rootClassType); + resolveSpecial(root, rootClassType, rootClassInfo); } else { objectStack.push(root); } @@ -322,8 +322,10 @@ private Schema readKlass(ClassInfo currentClass, return classSchema; } - private void resolveSpecial(DataObjectDeque.PathEntry root, Type type) { - if (typeArgumentMismatch(type, rootClassInfo)) { + private void resolveSpecial(DataObjectDeque.PathEntry root, Type type, ClassInfo standin) { + ParameterizedType standinType = standin.interfaceTypes().get(0).asParameterizedType(); + + if (typeArgumentMismatch(type, standin)) { /* * The type's generic arguments are not in alignment with the type * parameters of the stand-in collection type. For the purposes @@ -331,20 +333,19 @@ private void resolveSpecial(DataObjectDeque.PathEntry root, Type type) { * original type and determine the correct type parameter for the * stand-in. */ - ParameterizedType standinInterface = rootClassInfo.interfaceTypes().get(0).asParameterizedType(); - type = TypeResolver.resolveParameterizedAncestor(context, - type.asParameterizedType(), - standinInterface); + type = TypeResolver.resolveParameterizedAncestor(context, type, standinType) + .map(Type.class::cast) + .orElse(type); } - Map fieldResolution = TypeResolver.getAllFields(context, type, rootClassInfo, + Map fieldResolution = TypeResolver.getAllFields(context, type, standin, root.getAnnotationTarget()); rootSchema = preProcessSpecial(type, fieldResolution.values().iterator().next(), root); } private boolean typeArgumentMismatch(Type type, ClassInfo standin) { if (type.kind() != Kind.PARAMETERIZED_TYPE) { - return false; + return true; } return standin.typeParameters().size() < type.asParameterizedType().arguments().size(); @@ -360,14 +361,10 @@ private boolean isA(Type testSubject, Type test) { // Is Map, Collection, etc. private boolean isSpecialType(Type type) { - return isA(type, COLLECTION_TYPE) || isA(type, ITERABLE_TYPE) || isA(type, MAP_TYPE); + return isA(type, ITERABLE_TYPE) || isA(type, MAP_TYPE) || isA(type, STREAM_TYPE); } private ClassInfo initialType(Type type) { - if (isA(type, COLLECTION_TYPE)) { - return collectionStandin; - } - if (isA(type, ITERABLE_TYPE)) { return iterableStandin; } @@ -376,6 +373,10 @@ private ClassInfo initialType(Type type) { return mapStandin; } + if (isA(type, STREAM_TYPE)) { + return streamStandin; + } + return index.getClass(type); } diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/StreamStandin.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/StreamStandin.java new file mode 100644 index 000000000..edbf5a27e --- /dev/null +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/StreamStandin.java @@ -0,0 +1,7 @@ +package io.smallrye.openapi.runtime.scanner; + +import java.util.stream.BaseStream; + +public abstract class StreamStandin implements BaseStream> { + E value; +} diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeProcessor.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeProcessor.java index e90c3fdf1..423b5782e 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeProcessor.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeProcessor.java @@ -1,11 +1,11 @@ package io.smallrye.openapi.runtime.scanner.dataobject; import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.ARRAY_TYPE_OBJECT; -import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.COLLECTION_TYPE; import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.ENUM_TYPE; import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.ITERABLE_TYPE; import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.MAP_TYPE; import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.SET_TYPE; +import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.STREAM_TYPE; import static io.smallrye.openapi.runtime.scanner.OpenApiDataObjectScanner.STRING_TYPE; import static io.smallrye.openapi.runtime.util.TypeUtil.isTerminalType; @@ -115,19 +115,25 @@ public Type processType() { return readParameterizedType(type.asParameterizedType(), this.schema); } - // Raw Collection - if (isA(type, COLLECTION_TYPE)) { - return ARRAY_TYPE_OBJECT; - } - // Raw Iterable if (isA(type, ITERABLE_TYPE)) { - return ARRAY_TYPE_OBJECT; + return TypeResolver.resolveParameterizedAncestor(context, type, ITERABLE_TYPE) + .map(p -> readParameterizedType(p.asParameterizedType(), this.schema)) + .orElse(ARRAY_TYPE_OBJECT); + } + + // Raw Stream + if (isA(type, STREAM_TYPE)) { + return TypeResolver.resolveParameterizedAncestor(context, type, STREAM_TYPE) + .map(p -> readParameterizedType(p.asParameterizedType(), this.schema)) + .orElse(ARRAY_TYPE_OBJECT); } // Raw Map if (isA(type, MAP_TYPE)) { - return MAP_TYPE; + return TypeResolver.resolveParameterizedAncestor(context, type, MAP_TYPE) + .map(p -> readParameterizedType(p.asParameterizedType(), this.schema)) + .orElse(MAP_TYPE); } // Simple case: bare class or primitive type. @@ -187,18 +193,20 @@ private Type readArrayType(ArrayType arrayType, Schema arraySchema) { private Type readParameterizedType(ParameterizedType pType, Schema schema) { DataObjectLogging.logger.processingParametrizedType(pType); Type typeRead = pType; + Type seekType = resolveSeekType(pType); - // If it's a collection, we should treat it as an array. - if (isA(pType, COLLECTION_TYPE) || isA(pType, ITERABLE_TYPE)) { - DataObjectLogging.logger.processingTypeAs("Java Collection", "Array"); + // If it's a collection, iterable, or a stream, we should treat it as an array. + if (seekType != null && seekType != MAP_TYPE) { + DataObjectLogging.logger.processingTypeAs("Java Iterable or Stream", "Array"); schema.type(Schema.SchemaType.ARRAY); - ParameterizedType ancestorType = TypeResolver.resolveParameterizedAncestor(context, pType, ITERABLE_TYPE); + ParameterizedType ancestorType = TypeResolver.resolveParameterizedAncestor(context, pType, seekType) + .orElse(pType); if (TypeUtil.isA(context, pType, SET_TYPE)) { schema.setUniqueItems(Boolean.TRUE); } - // Should only have one arg for collection. + // Should only have one argument for Iterable and Stream uses first argument of BaseStream. Type valueType = ancestorType.arguments().get(0); boolean isOptional = TypeUtil.isOptional(valueType); if (isOptional) { @@ -213,10 +221,11 @@ private Type readParameterizedType(ParameterizedType pType, Schema schema) { schema.setItems(valueSchema); typeRead = ARRAY_TYPE_OBJECT; // Representing collection as JSON array - } else if (isA(pType, MAP_TYPE)) { + } else if (seekType == MAP_TYPE) { DataObjectLogging.logger.processingTypeAs("Map", "object"); schema.type(Schema.SchemaType.OBJECT); - ParameterizedType ancestorType = TypeResolver.resolveParameterizedAncestor(context, pType, MAP_TYPE); + ParameterizedType ancestorType = TypeResolver.resolveParameterizedAncestor(context, pType, seekType) + .orElse(pType); if (ancestorType.arguments().size() == 2) { Type valueType = ancestorType.arguments().get(1); @@ -238,6 +247,19 @@ private Type readParameterizedType(ParameterizedType pType, Schema schema) { return typeRead; } + private Type resolveSeekType(ParameterizedType pType) { + if (isA(pType, ITERABLE_TYPE)) { + return ITERABLE_TYPE; + } + if (isA(pType, MAP_TYPE)) { + return MAP_TYPE; + } + if (isA(pType, STREAM_TYPE)) { + return STREAM_TYPE; + } + return null; + } + private static Schema wrapOptionalItemSchema(Schema itemSchema) { return new SchemaImpl() .nullable(Boolean.TRUE) diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java index cc7dec787..838de4d77 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; @@ -26,8 +27,10 @@ import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.CompositeIndex; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; @@ -455,17 +458,15 @@ public static TypeResolver forClass(AnnotationScannerContext context, ClassInfo Type currentType = entry.getValue(); if (currentType.kind() == Type.Kind.PARAMETERIZED_TYPE) { - Map resMap = buildParamTypeResolutionMap(currentClass, currentType.asParameterizedType()); - stack.push(resMap); + stack.push(buildParamTypeResolutionMap(currentClass, currentType)); } // Add parameter type information from any interfaces implemented by this class/interface JandexUtil.interfaces(index, currentClass) .stream() .filter(type -> type.kind() == Type.Kind.PARAMETERIZED_TYPE) - .filter(type -> !TypeUtil.knownJavaType(type.name())) .filter(index::containsClass) - .map(type -> buildParamTypeResolutionMap(index.getClass(type), type.asParameterizedType())) + .map(type -> buildParamTypeResolutionMap(index.getClass(type), type)) .forEach(stack::push); if (allOfMatch || (!currentType.equals(clazzType) && TypeUtil.isIncludedAllOf(clazz, currentType))) { @@ -490,8 +491,7 @@ public static Map getAllFields(AnnotationScannerContext co Type currentType = entry.getValue(); if (currentType.kind() == Type.Kind.PARAMETERIZED_TYPE) { - Map resMap = buildParamTypeResolutionMap(currentClass, currentType.asParameterizedType()); - stack.push(resMap); + stack.push(buildParamTypeResolutionMap(currentClass, currentType)); } if (skipPropertyScan || (!currentType.equals(leaf) && TypeUtil.isIncludedAllOf(leafKlazz, currentType)) @@ -1105,13 +1105,22 @@ private static Map buildParamTypeResolutionMap(ClassInfo klazz, Pa return resolutionMap; } - public static ParameterizedType resolveParameterizedAncestor(AnnotationScannerContext context, ParameterizedType pType, + private static Map buildParamTypeResolutionMap(ClassInfo klazz, Type type) { + if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) { + return buildParamTypeResolutionMap(klazz, type.asParameterizedType()); + } + return Collections.emptyMap(); + } + + public static Optional resolveParameterizedAncestor(AnnotationScannerContext context, + Type type, Type seekType) { - ParameterizedType cursor = pType; + IndexView index = CompositeIndex.create(context.getAugmentedIndex(), TypeUtil.jdkIndex); + Type cursor = type; boolean seekContinue = true; + ClassInfo cursorClass; - while (context.getAugmentedIndex().containsClass(cursor) && seekContinue) { - ClassInfo cursorClass = context.getIndex().getClassByName(cursor.name()); + while ((cursorClass = index.getClassByName(cursor.name())) != null && seekContinue) { Map resolutionMap = buildParamTypeResolutionMap(cursorClass, cursor); List interfaces = getInterfacesOfType(context, cursorClass, seekType); @@ -1137,7 +1146,7 @@ public static ParameterizedType resolveParameterizedAncestor(AnnotationScannerCo } } - return cursor; + return cursor.kind() == Type.Kind.PARAMETERIZED_TYPE ? Optional.of(cursor.asParameterizedType()) : Optional.empty(); } private static List getInterfacesOfType(AnnotationScannerContext context, ClassInfo clazz, Type seekType) { diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AbstractParameterProcessor.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AbstractParameterProcessor.java index b3811aaee..827b4da55 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AbstractParameterProcessor.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AbstractParameterProcessor.java @@ -1481,7 +1481,7 @@ static MethodInfo targetMethod(AnnotationTarget target) { */ protected void readParametersInherited(ClassInfo clazz, AnnotationInstance beanParamAnnotation, boolean overriddenParametersOnly) { - AugmentedIndexView augmentedIndex = AugmentedIndexView.augment(index); + AugmentedIndexView augmentedIndex = scannerContext.getAugmentedIndex(); List ancestors = new ArrayList<>(JandexUtil.inheritanceChain(index, clazz, null).keySet()); /* * Process parent class(es) before the resource method class to allow for overridden parameter attributes. diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java index 4a731903b..6240d3c75 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java @@ -53,6 +53,7 @@ public AnnotationScannerContext(FilteredIndexView index, ClassLoader classLoader OpenAPI openApi) { this.index = index; this.augmentedIndex = AugmentedIndexView.augment(index); + //this.augmentedIndex = AugmentedIndexView.augment(CompositeIndex.create(index, TypeUtil.jdkIndex)); this.ignoreResolver = new IgnoreResolver(this.augmentedIndex); this.classLoader = classLoader; this.extensions = extensions; diff --git a/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java b/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java index 6b65a24c8..9d3a2d470 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java @@ -114,7 +114,7 @@ public class TypeUtil { .example("13:45.30.123456789").build(); private static final Map TYPE_MAP = new LinkedHashMap<>(); - private static final IndexView jdkIndex; + public static final IndexView jdkIndex; private static final Set wrapperTypes = new HashSet<>(); // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#dataTypeFormat @@ -264,6 +264,13 @@ public class TypeUtil { index(indexer, java.util.concurrent.PriorityBlockingQueue.class); index(indexer, java.util.concurrent.SynchronousQueue.class); + // Streams + index(indexer, java.util.stream.BaseStream.class); + index(indexer, java.util.stream.Stream.class); + index(indexer, java.util.stream.IntStream.class); + index(indexer, java.util.stream.LongStream.class); + index(indexer, java.util.stream.DoubleStream.class); + // CompletionStage and implementation index(indexer, java.util.concurrent.CompletionStage.class); index(indexer, java.util.concurrent.CompletableFuture.class); diff --git a/core/src/test/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScannerTest.java b/core/src/test/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScannerTest.java new file mode 100644 index 000000000..23349a95b --- /dev/null +++ b/core/src/test/java/io/smallrye/openapi/runtime/scanner/OpenApiDataObjectScannerTest.java @@ -0,0 +1,44 @@ +package io.smallrye.openapi.runtime.scanner; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.eclipse.microprofile.openapi.models.media.Schema; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import io.smallrye.openapi.runtime.scanner.spi.AnnotationScannerContext; + +class OpenApiDataObjectScannerTest { + + OpenApiDataObjectScanner target; + + @BeforeEach + void setUp() throws Exception { + } + + @ParameterizedTest + @CsvSource({ + "java.util.stream.DoubleStream, NUMBER, double", + "java.util.stream.IntStream , INTEGER, int32", + "java.util.stream.LongStream , INTEGER, int64", + "java.util.stream.Stream , , ", + }) + void testStreams(Class type, Schema.SchemaType itemType, String itemFormat) throws IOException { + IndexView index = Index.of(new Class[0]); + AnnotationScannerContext context = new AnnotationScannerContext(index, Thread.currentThread().getContextClassLoader(), + IndexScannerTestBase.emptyConfig()); + Schema out = OpenApiDataObjectScanner.process(context, Type.create(DotName.createSimple(type), Kind.CLASS)); + assertEquals(Schema.SchemaType.ARRAY, out.getType()); + assertEquals(itemType, out.getItems().getType()); + assertEquals(itemFormat, out.getItems().getFormat()); + } + +} diff --git a/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java b/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java index fa3a7e0b8..795d0a78c 100644 --- a/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java +++ b/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java @@ -14,8 +14,13 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.UUID; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; import org.eclipse.microprofile.openapi.annotations.media.DiscriminatorMapping; @@ -637,4 +642,35 @@ void testKotlinPropertyName() throws IOException, JSONException { printToConsole(result); assertJsonEquals("components.schemas.kotlin-value-class-propname.json", result); } + + /* + * https://github.com/smallrye/smallrye-open-api/issues/1359 + */ + @Test + @SuppressWarnings("unused") + void testStreamTypes() throws IOException, JSONException { + @SuppressWarnings("serial") + @Schema(name = "StringArray") + class StringArray extends ArrayList { + + } + + @Schema(name = "TestBean") + class Bean { + Stream stringArrayFromStream; + IntStream intArrayFromStream; + Properties anyValueMapFromProperties; + StringArray stringArrayFromIterator; + LongStream longArrayFromStream; + DoubleStream doubleArrayFromStream; + @SuppressWarnings("rawtypes") + Collection anyArrayFromRawCollection; + } + + Index index = indexOf(Bean.class, StringArray.class); + OpenApiAnnotationScanner scanner = new OpenApiAnnotationScanner(emptyConfig(), index); + OpenAPI result = scanner.scan(); + printToConsole(result); + assertJsonEquals("components.schemas.iterator-stream-map-types.json", result); + } } diff --git a/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.iterator-stream-map-types.json b/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.iterator-stream-map-types.json new file mode 100644 index 000000000..08dbab9d6 --- /dev/null +++ b/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas.iterator-stream-map-types.json @@ -0,0 +1,60 @@ +{ + "openapi": "3.0.3", + "components": { + "schemas": { + "StringArray": { + "type": "array", + "items": { + "type": "string" + } + }, + "TestBean": { + "type": "object", + "properties": { + "stringArrayFromStream": { + "type": "array", + "items": { + "type": "string" + } + }, + "intArrayFromStream": { + "type": "array", + "items": { + "format": "int32", + "type": "integer" + } + }, + "anyValueMapFromProperties": { + "type": "object", + "additionalProperties": { + + } + }, + "stringArrayFromIterator": { + "$ref": "#/components/schemas/StringArray" + }, + "longArrayFromStream": { + "type": "array", + "items": { + "format": "int64", + "type": "integer" + } + }, + "doubleArrayFromStream": { + "type": "array", + "items": { + "format": "double", + "type": "number" + } + }, + "anyArrayFromRawCollection": { + "type": "array", + "items": { + + } + } + } + } + } + } +} diff --git a/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JaxRsDataObjectScannerTestBase.java b/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JaxRsDataObjectScannerTestBase.java index 9dddfa7b8..e82f1eabe 100644 --- a/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JaxRsDataObjectScannerTestBase.java +++ b/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/scanner/JaxRsDataObjectScannerTestBase.java @@ -25,11 +25,6 @@ public class JaxRsDataObjectScannerTestBase extends IndexScannerTestBase { public static void createIndex() { Indexer indexer = new Indexer(); - // Stand-in stuff - index(indexer, "io/smallrye/openapi/runtime/scanner/CollectionStandin.class"); - index(indexer, "io/smallrye/openapi/runtime/scanner/IterableStandin.class"); - index(indexer, "io/smallrye/openapi/runtime/scanner/MapStandin.class"); - // Test samples indexDirectory(indexer, "test/io/smallrye/openapi/runtime/scanner/entities/"); indexDirectory(indexer, "test/io/smallrye/openapi/runtime/scanner/entities/jakarta/"); diff --git a/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/util/TypeUtilTest.java b/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/util/TypeUtilTest.java index a7819a9ed..f1bd59be6 100644 --- a/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/util/TypeUtilTest.java +++ b/extension-jaxrs/src/test/java/io/smallrye/openapi/runtime/util/TypeUtilTest.java @@ -20,7 +20,7 @@ class TypeUtilTest extends IndexScannerTestBase { - private static final Type TYPE_COLLECTION = OpenApiDataObjectScanner.COLLECTION_TYPE; + private static final Type ITERABLE_TYPE = OpenApiDataObjectScanner.ITERABLE_TYPE; private static final Type TYPE_ENUM = OpenApiDataObjectScanner.ENUM_TYPE; private static final Type TYPE_MAP = OpenApiDataObjectScanner.MAP_TYPE; @@ -29,7 +29,7 @@ void testIsA_BothIndexed() { final Class subjectClass = ArrayCollection.class; Index index = indexOf(subjectClass, Collection.class); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertTrue(result); } @@ -38,7 +38,7 @@ void testIsA_SubjectIndexed() { final Class subjectClass = ArrayCollection.class; Index index = indexOf(subjectClass); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertTrue(result); } @@ -47,7 +47,7 @@ void testIsA_ObjectIndexed() { final Class subjectClass = ArrayCollection.class; Index index = indexOf(Collection.class); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertTrue(result); } @@ -56,7 +56,7 @@ void testIsA_IndexedSubjectImplementsObject() { final Class subjectClass = CustomCollection.class; Index index = indexOf(subjectClass); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertTrue(result); } @@ -65,7 +65,7 @@ void testIsA_IndexedSubjectImplementsOther() { final Class subjectClass = CustomMap.class; Index index = indexOf(subjectClass); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertFalse(result); } @@ -74,7 +74,7 @@ void testIsA_IndexedSubjectExtendsUnindexedCollection() { final Class subjectClass = ChildCollection.class; Index index = indexOf(subjectClass); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertTrue(result); } @@ -83,7 +83,7 @@ void testIsA_IndexedSubjectUnrelatedToObject() { final Class subjectClass = UnrelatedType.class; Index index = indexOf(subjectClass); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertFalse(result); } @@ -92,7 +92,7 @@ void testIsA_UnindexedPrimitiveSubjectUnrelatedToObject() { final Class subjectClass = int.class; Index index = indexOf(); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.PRIMITIVE); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertFalse(result); } @@ -102,7 +102,7 @@ void testIsA_UnindexedPrimitiveWrapperSubjectUnrelatedToObject() { final DotName subjectName = DotName.createSimple(subjectClass.getName()); Index index = indexOf(); Type testSubject = Type.create(subjectName, Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertFalse(result); } @@ -111,7 +111,7 @@ void testIsA_SubjectIsJavaLangObject() { final Class subjectClass = Object.class; Index index = indexOf(subjectClass); Type testSubject = Type.create(DotName.createSimple(subjectClass.getName()), Type.Kind.CLASS); - boolean result = isA(index, testSubject, TYPE_COLLECTION); + boolean result = isA(index, testSubject, ITERABLE_TYPE); assertFalse(result); } diff --git a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/AllTheParamsTestResource.java b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/AllTheParamsTestResource.java index 65902ecdc..7fbb949b4 100644 --- a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/AllTheParamsTestResource.java +++ b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/AllTheParamsTestResource.java @@ -1,6 +1,7 @@ package test.io.smallrye.openapi.runtime.scanner.jakarta; import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.BeanParam; @@ -68,4 +69,11 @@ public Widget get(@QueryParam(value = "q1") @Deprecated long q1, return null; } + @GET + @Path("stream") + @Produces(value = MediaType.APPLICATION_JSON) + public Stream getStream(@QueryParam(value = "q1") @Deprecated long q1, + @org.jboss.resteasy.annotations.jaxrs.QueryParam(value = "q2") String notQ2) { + return null; + } } diff --git a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/RestEasyReactiveAllTheParamsTestResource.java b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/RestEasyReactiveAllTheParamsTestResource.java index afadaebce..01ab3d0fb 100644 --- a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/RestEasyReactiveAllTheParamsTestResource.java +++ b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/jakarta/RestEasyReactiveAllTheParamsTestResource.java @@ -1,6 +1,7 @@ package test.io.smallrye.openapi.runtime.scanner.jakarta; import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.BeanParam; @@ -67,4 +68,11 @@ public RestResponse get(@RestQuery(value = "q1") @Deprecated long q1, @R return null; } + @GET + @Path("stream") + @Produces(value = MediaType.APPLICATION_JSON) + public RestResponse> getStream(@RestQuery(value = "q1") @Deprecated long q1, + @RestQuery(value = "q2") String notQ2) { + return null; + } } diff --git a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/AllTheParamsTestResource.java b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/AllTheParamsTestResource.java index 7203bb5dc..618b0f662 100644 --- a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/AllTheParamsTestResource.java +++ b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/AllTheParamsTestResource.java @@ -1,6 +1,7 @@ package test.io.smallrye.openapi.runtime.scanner.javax; import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; import javax.validation.constraints.NotNull; import javax.ws.rs.BeanParam; @@ -68,4 +69,11 @@ public Widget get(@QueryParam(value = "q1") @Deprecated long q1, return null; } + @GET + @Path("stream") + @Produces(value = MediaType.APPLICATION_JSON) + public Stream getStream(@QueryParam(value = "q1") @Deprecated long q1, + @org.jboss.resteasy.annotations.jaxrs.QueryParam(value = "q2") String notQ2) { + return null; + } } diff --git a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/RestEasyReactiveAllTheParamsTestResource.java b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/RestEasyReactiveAllTheParamsTestResource.java index 5b619b08a..9550157ab 100644 --- a/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/RestEasyReactiveAllTheParamsTestResource.java +++ b/extension-jaxrs/src/test/java/test/io/smallrye/openapi/runtime/scanner/javax/RestEasyReactiveAllTheParamsTestResource.java @@ -1,6 +1,7 @@ package test.io.smallrye.openapi.runtime.scanner.javax; import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; import javax.validation.constraints.NotNull; import javax.ws.rs.BeanParam; @@ -67,4 +68,11 @@ public RestResponse get(@RestQuery(value = "q1") @Deprecated long q1, @R return null; } + @GET + @Path("stream") + @Produces(value = MediaType.APPLICATION_JSON) + public RestResponse> getAll(@RestQuery(value = "q1") @Deprecated long q1, + @RestQuery(value = "q2") String notQ2) { + return null; + } } diff --git a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.synthetic-classes-interfaces.json b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.synthetic-classes-interfaces.json index 61ae74580..cb093c57e 100644 --- a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.synthetic-classes-interfaces.json +++ b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/ignore.synthetic-classes-interfaces.json @@ -30,7 +30,10 @@ "name": "sort", "in": "query", "schema": { - "type": "array" + "type": "array", + "items": { + + } } } ], diff --git a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/params.all-the-params.json b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/params.all-the-params.json index ebbe2a3b4..5947671f3 100644 --- a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/params.all-the-params.json +++ b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/params.all-the-params.json @@ -142,6 +142,90 @@ } } } + }, + "/all/the/params/{id1}/{id2}{id2Matrix}/stream": { + "parameters": [ + { + "name": "id1", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "id2", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "id2Matrix", + "in": "path", + "style": "matrix", + "required": true, + "schema": { + "type": "object", + "properties": { + "matrixF1": { + "type": "string", + "default": "BEAN1" + }, + "matrixF2": { + "type": "string", + "default": "BEAN2" + } + } + } + }, + { + "name": "cookieF1", + "in": "cookie", + "deprecated": true, + "schema": { + "type": "string", + "default": "COOKIE1" + } + } + ], + "get": { + "parameters": [ + { + "name": "q1", + "in": "query", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "q2", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Widget" + } + } + } + } + } + } + } } }, "components": { diff --git a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/refsEnabled.kitchenSink.expected.json b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/refsEnabled.kitchenSink.expected.json index 64aea62b5..f2b3df8c2 100644 --- a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/refsEnabled.kitchenSink.expected.json +++ b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/refsEnabled.kitchenSink.expected.json @@ -21,11 +21,13 @@ "type": "object", "properties": { "theQ": { + }, "theT": { "$ref": "#/components/schemas/Baz" }, "ultimateTShouldBeQ": { + } } }, @@ -184,8 +186,10 @@ "type": "object", "properties": { "qAgain": { + }, "qAgain3": { + }, "qValue": { "description": "Ah, Q, my favourite variable!" @@ -301,6 +305,7 @@ "type": "object", "properties": { "bar": { + }, "foo": { "maxLength": 123456, @@ -412,10 +417,14 @@ "barSuper": { "type": "array", "items": { + } }, "bareCollection": { - "type": "array" + "type": "array", + "items": { + + } }, "bareEnum": { "type": "array", @@ -426,6 +435,7 @@ "blahMap": { "type": "object", "additionalProperties": { + } }, "booking": { @@ -498,7 +508,10 @@ "$ref": "#/components/schemas/KustomPairStringInteger" }, "unsafeList": { - "type": "array" + "type": "array", + "items" : { + + } }, "voidField": { "type": "object" diff --git a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/responses.component-status-reuse.json b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/responses.component-status-reuse.json index 5e5fcb8a3..7fb1aa6e4 100644 --- a/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/responses.component-status-reuse.json +++ b/extension-jaxrs/src/test/resources/io/smallrye/openapi/runtime/scanner/responses.component-status-reuse.json @@ -23,7 +23,8 @@ "content": { "application/json": { "schema": { - "type": "object" + "type": "object", + "additionalProperties": { } } } } diff --git a/testsuite/extra/src/test/java/test/io/smallrye/openapi/tck/ArchiveUtil.java b/testsuite/extra/src/test/java/test/io/smallrye/openapi/tck/ArchiveUtil.java index 87f81cc10..910e42926 100644 --- a/testsuite/extra/src/test/java/test/io/smallrye/openapi/tck/ArchiveUtil.java +++ b/testsuite/extra/src/test/java/test/io/smallrye/openapi/tck/ArchiveUtil.java @@ -3,7 +3,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.util.Map; import java.util.Set; @@ -25,7 +24,6 @@ import io.smallrye.openapi.runtime.OpenApiStaticFile; import io.smallrye.openapi.runtime.io.Format; import io.smallrye.openapi.runtime.scanner.FilteredIndexView; -import io.smallrye.openapi.runtime.scanner.OpenApiAnnotationScanner; /** * Some useful methods for creating stuff from ShrinkWrap {@link Archive}s. @@ -107,22 +105,10 @@ public static IndexView archiveToIndex(OpenApiConfig config, Archive archive) } Indexer indexer = new Indexer(); - index(indexer, "io/smallrye/openapi/runtime/scanner/CollectionStandin.class"); - index(indexer, "io/smallrye/openapi/runtime/scanner/IterableStandin.class"); - index(indexer, "io/smallrye/openapi/runtime/scanner/MapStandin.class"); indexArchive(config, indexer, archive); return indexer.complete(); } - private static void index(Indexer indexer, String resName) { - ClassLoader cl = OpenApiAnnotationScanner.class.getClassLoader(); - try (InputStream klazzStream = cl.getResourceAsStream(resName)) { - indexer.index(klazzStream); - } catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } - } - /** * Indexes the given archive. *