Skip to content

Commit

Permalink
Represent Java Stream<T> as array of T in generated schema
Browse files Browse the repository at this point in the history
Fixes smallrye#1359

Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar committed Jan 18, 2023
1 parent b29019e commit 1000c0b
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 61 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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.Stream;

import org.eclipse.microprofile.openapi.models.media.Schema;
import org.eclipse.microprofile.openapi.models.media.Schema.SchemaType;
Expand Down Expand Up @@ -69,43 +69,50 @@ public class OpenApiDataObjectScanner {

// Object
public static final Type OBJECT_TYPE = Type.create(DotName.createSimple(java.lang.Object.class.getName()), Type.Kind.CLASS);
// 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)

// Iterable (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(Stream.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);

// Set
public static final DotName SET_INTERFACE_NAME = DotName.createSimple(java.util.Set.class.getName());
public static final Type SET_TYPE = Type.create(SET_INTERFACE_NAME, Type.Kind.CLASS);

// Enum
public static final DotName ENUM_INTERFACE_NAME = DotName.createSimple(Enum.class.getName());
public static final Type ENUM_TYPE = Type.create(ENUM_INTERFACE_NAME, Type.Kind.CLASS);

// String type
public static final Type STRING_TYPE = Type.create(DotName.createSimple(String.class.getName()), Type.Kind.CLASS);

// Array type
public static final Type ARRAY_TYPE_OBJECT = Type.create(DotName.createSimple("[Ljava.lang.Object;"), 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
* collections of application classes (indexed elsewhere).
*/
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) {
Expand Down Expand Up @@ -356,14 +363,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;
}
Expand All @@ -372,6 +375,10 @@ private ClassInfo initialType(Type type) {
return mapStandin;
}

if (isA(type, STREAM_TYPE)) {
return streamStandin;
}

return index.getClass(type);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.smallrye.openapi.runtime.scanner;

import java.util.stream.Stream;

public abstract class StreamStandin<E> implements Stream<E> {
E value;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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.OBJECT_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;

Expand Down Expand Up @@ -116,13 +116,13 @@ public Type processType() {
return readParameterizedType(type.asParameterizedType(), this.schema);
}

// Raw Collection
if (isA(type, COLLECTION_TYPE)) {
// Raw Iterable
if (isA(type, ITERABLE_TYPE)) {
return ARRAY_TYPE_OBJECT;
}

// Raw Iterable
if (isA(type, ITERABLE_TYPE)) {
// Raw Stream
if (isA(type, STREAM_TYPE)) {
return ARRAY_TYPE_OBJECT;
}

Expand Down Expand Up @@ -188,12 +188,14 @@ private Type readArrayType(ArrayType arrayType, Schema arraySchema) {
private Type readParameterizedType(ParameterizedType pType, Schema schema) {
DataObjectLogging.logger.processingParametrizedType(pType);
Type typeRead = pType;
final boolean isIterable = isA(pType, ITERABLE_TYPE);

// If it's a collection, we should treat it as an array.
if (isA(pType, COLLECTION_TYPE) || isA(pType, ITERABLE_TYPE)) {
// If it's a collection, iterable, or a stream, we should treat it as an array.
if (isIterable || isA(pType, STREAM_TYPE)) {
DataObjectLogging.logger.processingTypeAs("Java Collection", "Array");
schema.type(Schema.SchemaType.ARRAY);
ParameterizedType ancestorType = TypeResolver.resolveParameterizedAncestor(context, pType, ITERABLE_TYPE);
ParameterizedType ancestorType = TypeResolver.resolveParameterizedAncestor(context, pType,
isIterable ? ITERABLE_TYPE : STREAM_TYPE);

if (TypeUtil.isA(context, pType, SET_TYPE)) {
schema.setUniqueItems(Boolean.TRUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Widget> getStream(@QueryParam(value = "q1") @Deprecated long q1,
@org.jboss.resteasy.annotations.jaxrs.QueryParam(value = "q2") String notQ2) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -67,4 +68,11 @@ public RestResponse<Widget> get(@RestQuery(value = "q1") @Deprecated long q1, @R
return null;
}

@GET
@Path("stream")
@Produces(value = MediaType.APPLICATION_JSON)
public RestResponse<Stream<Widget>> getStream(@RestQuery(value = "q1") @Deprecated long q1,
@RestQuery(value = "q2") String notQ2) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Widget> getStream(@QueryParam(value = "q1") @Deprecated long q1,
@org.jboss.resteasy.annotations.jaxrs.QueryParam(value = "q2") String notQ2) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -67,4 +68,11 @@ public RestResponse<Widget> get(@RestQuery(value = "q1") @Deprecated long q1, @R
return null;
}

@GET
@Path("stream")
@Produces(value = MediaType.APPLICATION_JSON)
public RestResponse<Stream<Widget>> getAll(@RestQuery(value = "q1") @Deprecated long q1,
@RestQuery(value = "q2") String notQ2) {
return null;
}
}
Loading

0 comments on commit 1000c0b

Please sign in to comment.