From 4a14dc2697016fcef006b55d8726126c899ee232 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 21 Mar 2024 14:59:08 +0000 Subject: [PATCH] Extract TypePair in SchemaMappingInspector Refactor logic to determine the GraphQL type and Java type for a field into private, nested class. See gh-924 --- .../execution/SchemaMappingInspector.java | 207 ++++++++++-------- 1 file changed, 118 insertions(+), 89 deletions(-) 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 d9f0ec29..9d5a435a 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 @@ -93,8 +93,6 @@ public class SchemaMappingInspector { private final Set inspectedTypes = new HashSet<>(); - private final ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); - private final ReportBuilder reportBuilder = new ReportBuilder(); @Nullable @@ -171,49 +169,34 @@ public class SchemaMappingInspector { private void checkField( GraphQLFieldsContainer parent, GraphQLFieldDefinition field, DataFetcher dataFetcher) { - ResolvableType resolvableType = ResolvableType.NONE; if (dataFetcher instanceof SelfDescribingDataFetcher selfDescribing) { - resolvableType = selfDescribing.getReturnType(); checkFieldArguments(field, selfDescribing); } - // Remove GraphQL type wrappers, and nest within Java generic types - GraphQLType outputType = unwrapIfNonNull(field.getType()); - if (isPaginatedType(outputType)) { - outputType = getPaginatedType((GraphQLObjectType) outputType); - resolvableType = nestForConnection(resolvableType); - } - else if (outputType instanceof GraphQLList listType) { - outputType = unwrapIfNonNull(listType.getWrappedType()); - resolvableType = nestForList(resolvableType, (parent == this.schema.getSubscriptionType())); - } - else { - resolvableType = nestIfWrappedType(resolvableType); - } + TypePair typePair = TypePair.resolveTypePair(parent, field, dataFetcher, schema); // Type already inspected? - if (addAndCheckIfAlreadyInspected(outputType)) { + if (addAndCheckIfAlreadyInspected(typePair.outputType())) { return; } // Can we inspect GraphQL type? - if (!(outputType instanceof GraphQLFieldsContainer fieldContainer)) { - if (isNotScalarOrEnumType(outputType)) { + if (!(typePair.outputType() instanceof GraphQLFieldsContainer fieldContainer)) { + if (isNotScalarOrEnumType(typePair.outputType())) { FieldCoordinates coordinates = FieldCoordinates.coordinates(parent.getName(), field.getName()); - addSkippedType(outputType, coordinates, "Unsupported schema type"); + addSkippedType(typePair.outputType(), coordinates, "Unsupported schema type"); } return; } // Can we inspect Java type? - if (resolvableType.resolve(Object.class) == Object.class) { + if (typePair.resolvableType().resolve(Object.class) == Object.class) { FieldCoordinates coordinates = FieldCoordinates.coordinates(parent.getName(), field.getName()); - addSkippedType(outputType, coordinates, "No Java type information"); + addSkippedType(typePair.outputType(), coordinates, "No Java type information"); return; } - // Nest within the - checkFieldsContainer(fieldContainer, resolvableType); + checkFieldsContainer(fieldContainer, typePair.resolvableType()); } private void checkFieldArguments(GraphQLFieldDefinition field, SelfDescribingDataFetcher dataFetcher) { @@ -227,71 +210,7 @@ public class SchemaMappingInspector { } } - private GraphQLType unwrapIfNonNull(GraphQLType type) { - return (type instanceof GraphQLNonNull graphQLNonNull ? graphQLNonNull.getWrappedType() : type); - } - private boolean isPaginatedType(GraphQLType type) { - return (type instanceof GraphQLObjectType objectType && - objectType.getName().endsWith("Connection") && - objectType.getField("edges") != null && objectType.getField("pageInfo") != null); - } - - private GraphQLType getPaginatedType(GraphQLObjectType type) { - String name = type.getName().substring(0, type.getName().length() - 10); - GraphQLType nodeType = this.schema.getType(name); - Assert.state(nodeType != null, "No node type for '" + type.getName() + "'"); - return nodeType; - } - - private ResolvableType nestForConnection(ResolvableType type) { - if (type == ResolvableType.NONE) { - return type; - } - type = nestIfWrappedType(type); - if (logger.isDebugEnabled() && type.getGenerics().length != 1) { - logger.debug("Expected Connection type to have a generic parameter: " + type); - } - return type.getNested(2); - } - - private ResolvableType nestIfWrappedType(ResolvableType type) { - Class clazz = type.resolve(Object.class); - if (Optional.class.isAssignableFrom(clazz)) { - if (logger.isDebugEnabled() && type.getGeneric(0).resolve() == null) { - logger.debug("Expected Optional type to have a generic parameter: " + type); - } - return type.getNested(2); - } - ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(clazz); - if (adapter != null) { - if (logger.isDebugEnabled() && adapter.isNoValue()) { - logger.debug("Expected reactive/async return type that can produce value(s): " + type); - } - return type.getNested(2); - } - return type; - } - - private ResolvableType nestForList(ResolvableType type, boolean subscription) { - if (type == ResolvableType.NONE) { - return type; - } - ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(type.resolve(Object.class)); - if (adapter != null) { - if (logger.isDebugEnabled() && adapter.isNoValue()) { - logger.debug("Expected List compatible type: " + type); - } - type = type.getNested(2); - if (adapter.isMultiValue() && !subscription) { - return type; - } - } - if (logger.isDebugEnabled() && !type.isArray() && type.getGenerics().length != 1) { - logger.debug("Expected List compatible type: " + type); - } - return type.getNested(2); - } private static String typeNameToString(GraphQLType type) { return (type instanceof GraphQLNamedType namedType ? namedType.getName() : type.toString()); @@ -355,6 +274,116 @@ public class SchemaMappingInspector { } + /** + * Container for a GraphQL and Java type pair along with logic to resolve the + * pair of types for a GraphQL field and the {@code DataFetcher} registered for it. + */ + private record TypePair(GraphQLType outputType, ResolvableType resolvableType) { + + private static final ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); + + /** + * Given a GraphQL field and the {@link DataFetcher} registered for it, determine + * the type pair to use for schema inspection, removing list, non-null, and + * connection type wrappers, and nesting within generic types in order to get + * to the types to use for schema inspection. + * @param parent the parent type of the field + * @param field the field + * @param fetcher the {@code DataFetcher} registered for the field + * @param schema the GraphQL schema + * @return the GraphQL type and corresponding Java type, or {@link ResolvableType#NONE} if unresolved. + */ + public static TypePair resolveTypePair( + GraphQLType parent, GraphQLFieldDefinition field, DataFetcher fetcher, GraphQLSchema schema) { + + ResolvableType resolvableType = + (fetcher instanceof SelfDescribingDataFetcher sd ? sd.getReturnType() : ResolvableType.NONE); + + // Remove GraphQL type wrappers, and nest within Java generic types + GraphQLType outputType = unwrapIfNonNull(field.getType()); + if (isPaginatedType(outputType)) { + outputType = getPaginatedType((GraphQLObjectType) outputType, schema); + resolvableType = nestForConnection(resolvableType); + } + else if (outputType instanceof GraphQLList listType) { + outputType = unwrapIfNonNull(listType.getWrappedType()); + resolvableType = nestForList(resolvableType, parent == schema.getSubscriptionType()); + } + else { + resolvableType = nestIfWrappedType(resolvableType); + } + return new TypePair(outputType, resolvableType); + } + + private static GraphQLType unwrapIfNonNull(GraphQLType type) { + return (type instanceof GraphQLNonNull graphQLNonNull ? graphQLNonNull.getWrappedType() : type); + } + + private static boolean isPaginatedType(GraphQLType type) { + return (type instanceof GraphQLObjectType objectType && + objectType.getName().endsWith("Connection") && + objectType.getField("edges") != null && objectType.getField("pageInfo") != null); + } + + private static GraphQLType getPaginatedType(GraphQLObjectType type, GraphQLSchema schema) { + String name = type.getName().substring(0, type.getName().length() - 10); + GraphQLType nodeType = schema.getType(name); + Assert.state(nodeType != null, "No node type for '" + type.getName() + "'"); + return nodeType; + } + + private static ResolvableType nestForConnection(ResolvableType type) { + if (type == ResolvableType.NONE) { + return type; + } + type = nestIfWrappedType(type); + if (logger.isDebugEnabled() && type.getGenerics().length != 1) { + logger.debug("Expected Connection type to have a generic parameter: " + type); + } + return type.getNested(2); + } + + private static ResolvableType nestIfWrappedType(ResolvableType type) { + Class clazz = type.resolve(Object.class); + if (Optional.class.isAssignableFrom(clazz)) { + if (logger.isDebugEnabled() && type.getGeneric(0).resolve() == null) { + logger.debug("Expected Optional type to have a generic parameter: " + type); + } + return type.getNested(2); + } + ReactiveAdapter adapter = adapterRegistry.getAdapter(clazz); + if (adapter != null) { + if (logger.isDebugEnabled() && adapter.isNoValue()) { + logger.debug("Expected reactive/async return type that can produce value(s): " + type); + } + return type.getNested(2); + } + return type; + } + + private static ResolvableType nestForList(ResolvableType type, boolean subscription) { + if (type == ResolvableType.NONE) { + return type; + } + ReactiveAdapter adapter = adapterRegistry.getAdapter(type.resolve(Object.class)); + if (adapter != null) { + if (logger.isDebugEnabled() && adapter.isNoValue()) { + logger.debug("Expected List compatible type: " + type); + } + type = type.getNested(2); + if (adapter.isMultiValue() && !subscription) { + return type; + } + } + if (logger.isDebugEnabled() && !type.isArray() && type.getGenerics().length != 1) { + logger.debug("Expected List compatible type: " + type); + } + return type.getNested(2); + } + + }; + + /** * Helps to build a {@link SchemaReport}. */