diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java index b00362bc..745c2691 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java @@ -134,11 +134,14 @@ class SchemaMappingInspector { type = unwrapNonNull(type); if (isConnectionType(type)) { type = getConnectionNodeType(type); - resolvableType = nest(resolvableType, type); + resolvableType = nestForConnection(resolvableType, type); } else if (type instanceof GraphQLList listType) { type = unwrapNonNull(listType.getWrappedType()); - resolvableType = nest(resolvableType, type); + resolvableType = nestForList(resolvableType, type); + } + else { + resolvableType = (resolvableType != null ? nestIfReactive(resolvableType) : null); } if (type instanceof GraphQLNamedOutputType outputType) { @@ -157,7 +160,7 @@ class SchemaMappingInspector { } return; } - else if (resolvableType != null && resolveClassToCompare(resolvableType) == Object.class) { + else if (resolvableType != null && resolvableType.resolve(Object.class) == Object.class) { if (logger.isDebugEnabled()) { logger.debug("Skipped '" + getTypeName(type) + "': " + "inspection could not determine the Java object return type."); @@ -208,10 +211,35 @@ class SchemaMappingInspector { return type; } - private static ResolvableType nest(@Nullable ResolvableType resolvableType, GraphQLType type) { - Assert.notNull(resolvableType, "No declaredType for " + getTypeName(type)); - resolvableType = resolvableType.getNested(2); - return resolvableType; + private ResolvableType nestForConnection(@Nullable ResolvableType type, GraphQLType graphQLType) { + Assert.state(type != null, "No Java type for " + getTypeName(graphQLType)); + type = nestIfReactive(type); + Assert.state(type.hasGenerics(), "Expected type with generics: " + type); + return type.getNested(2); + } + + private ResolvableType nestIfReactive(ResolvableType type) { + Class clazz = type.resolve(Object.class); + ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(clazz); + if (adapter != null) { + Assert.state(!adapter.isNoValue(), "Expected value producing type: " + type); + return type.getNested(2); + } + return type; + } + + private ResolvableType nestForList(@Nullable ResolvableType type, GraphQLType graphQlType) { + Assert.state(type != null, "No Java type for " + getTypeName(graphQlType)); + ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(type.resolve(Object.class)); + if (adapter != null) { + if (adapter.isMultiValue()) { + return type.getNested(2); + } + Assert.state(!adapter.isNoValue(), "Expected List compatible type: " + type); + type = type.getNested(2); + } + Assert.state(type.isArray() || type.hasGenerics(), "Expected List compatible type: " + type); + return type.getNested(2); } private static String getTypeName(GraphQLType type) { @@ -224,7 +252,7 @@ class SchemaMappingInspector { private boolean hasProperty(ResolvableType resolvableType, String fieldName) { try { - Class clazz = resolveClassToCompare(resolvableType); + Class clazz = resolvableType.resolve(Object.class); return (BeanUtils.getPropertyDescriptor(clazz, fieldName) != null); } catch (BeansException ex) { @@ -233,12 +261,6 @@ class SchemaMappingInspector { } } - private Class resolveClassToCompare(ResolvableType resolvableType) { - Class clazz = resolvableType.resolve(Object.class); - ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(clazz); - return (adapter != null ? resolvableType.getNested(2).resolve(Object.class) : clazz); - } - @SuppressWarnings("rawtypes") private void inspectDataFetcherRegistrations() { this.runtimeWiring.getDataFetchers().forEach((typeName, registrations) -> diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java index 9bde1ea6..c4e1e8ac 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; import graphql.schema.FieldCoordinates; import graphql.schema.GraphQLSchema; @@ -28,7 +29,7 @@ import graphql.schema.idl.SchemaGenerator; import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; +import reactor.core.publisher.Flux; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.data.domain.OffsetScrollPosition; @@ -597,8 +598,8 @@ class SchemaMappingInspectorTests { } @SubscriptionMapping - public Mono bookSearch(@Argument String author) { - return Mono.empty(); + public Flux bookSearch(@Argument String author) { + return Flux.empty(); } } @@ -611,8 +612,8 @@ class SchemaMappingInspectorTests { } @SchemaMapping - public List members(Team team) { - return List.of(); + public CompletableFuture> members(Team team) { + return CompletableFuture.completedFuture(List.of()); } @SchemaMapping