diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java index 96a38e0d..e078995d 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java @@ -135,9 +135,9 @@ final class DefaultSchemaResourceGraphQlSourceBuilder configureGraphQl(builder -> { GraphQLSchema schema = builder.build().getGraphQLSchema(); - SchemaMappingInspector.Report schemaInspectionReport = new SchemaMappingInspector().inspectSchemaMappings(schema, runtimeWiring); - if(!schemaInspectionReport.isEmpty()) { - logger.info(schemaInspectionReport.getSummary()); + SchemaMappingInspector.Report report = SchemaMappingInspector.inspect(schema, runtimeWiring); + if (!report.isEmpty()) { + logger.info(report.getSummary()); } }); 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 6b89f4ab..33384ce7 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 @@ -64,124 +64,139 @@ class SchemaMappingInspector { private static final Log logger = LogFactory.getLog(SchemaMappingInspector.class); - Report inspectSchemaMappings(GraphQLSchema schema, RuntimeWiring runtimeWiring) { + + private final GraphQLSchema schema; + + private final RuntimeWiring runtimeWiring; + + private final Set seenTypes = new HashSet<>(); + + + private SchemaMappingInspector(GraphQLSchema schema, RuntimeWiring runtimeWiring) { + this.schema = schema; + this.runtimeWiring = runtimeWiring; + } + + + public Report inspectMappings() { ReportBuilder report = ReportBuilder.create(); - SchemaMappingInspection inspection = new SchemaMappingInspection(runtimeWiring); - inspection.inspectOperation(schema.getQueryType(), report); - inspection.inspectOperation(schema.getMutationType(), report); - inspection.inspectOperation(schema.getSubscriptionType(), report); + inspectOperation(schema.getQueryType(), report); + inspectOperation(schema.getMutationType(), report); + inspectOperation(schema.getSubscriptionType(), report); return report.build(); } - private static class SchemaMappingInspection { - private final RuntimeWiring runtimeWiring; - - private final Set seenTypes = new HashSet<>(); - - SchemaMappingInspection(RuntimeWiring runtimeWiring) { - this.runtimeWiring = runtimeWiring; - } - - @SuppressWarnings("rawtypes") - void inspectOperation(@Nullable GraphQLObjectType operationType, ReportBuilder report) { - if (operationType != null) { - Map operationDataFetchers = this.runtimeWiring.getDataFetcherForType(operationType.getName()); - for (GraphQLFieldDefinition fieldDefinition : operationType.getFieldDefinitions()) { - if (operationDataFetchers.containsKey(fieldDefinition.getName())) { - DataFetcher fieldDataFetcher = operationDataFetchers.get(fieldDefinition.getName()); - if (fieldDataFetcher instanceof SelfDescribingDataFetcher selfDescribingDataFetcher) { - inspectType(fieldDefinition.getType(), selfDescribingDataFetcher.getReturnType(), report); - } - } - else { - report.missingOperation(operationType, fieldDefinition); - } - } - } - } - - private void inspectType(GraphQLType type, ResolvableType declaredType, ReportBuilder report) { - if (type instanceof GraphQLObjectType objectType) { - inspectObjectType(objectType, declaredType, report); - } - else if (type instanceof GraphQLList listType) { - inspectType(listType.getWrappedType(), declaredType.getNested(2), report); - } - else if (type instanceof GraphQLNonNull nonNullType) { - inspectType(nonNullType.getWrappedType(), declaredType, report); - } - else if (type instanceof GraphQLNamedType namedType && logger.isTraceEnabled()){ - logger.trace("Cannot inspect type '" + namedType.getName() + "', inspector does not support " - + type.getClass().getSimpleName()); - } - } - - @SuppressWarnings("rawtypes") - private void inspectObjectType(GraphQLObjectType objectType, ResolvableType declaredType, ReportBuilder report) { - if (isTypeAlreadyInspected(objectType)) { - return; - } - if (isConnectionType(objectType)) { - objectType = getEdgeNodeType(objectType); - declaredType = declaredType.getNested(2); - } - Map typeDataFetcher = this.runtimeWiring.getDataFetcherForType(objectType.getName()); - Class declaredClass = unwrapPublisherTypes(declaredType); - for (GraphQLFieldDefinition field : objectType.getFieldDefinitions()) { - if (typeDataFetcher.containsKey(field.getName())) { - DataFetcher fieldDataFetcher = typeDataFetcher.get(field.getName()); - if (fieldDataFetcher instanceof SelfDescribingDataFetcher typedFieldDataFetcher) { - inspectType(field.getType(), typedFieldDataFetcher.getReturnType(), report); + @SuppressWarnings("rawtypes") + private void inspectOperation(@Nullable GraphQLObjectType operationType, ReportBuilder report) { + if (operationType != null) { + Map operationDataFetchers = this.runtimeWiring.getDataFetcherForType(operationType.getName()); + for (GraphQLFieldDefinition fieldDefinition : operationType.getFieldDefinitions()) { + if (operationDataFetchers.containsKey(fieldDefinition.getName())) { + DataFetcher fieldDataFetcher = operationDataFetchers.get(fieldDefinition.getName()); + if (fieldDataFetcher instanceof SelfDescribingDataFetcher selfDescribingDataFetcher) { + inspectType(fieldDefinition.getType(), selfDescribingDataFetcher.getReturnType(), report); } } else { - try { - if (declaredClass == null || BeanUtils.getPropertyDescriptor(declaredClass, field.getName()) == null) { - report.missingField(objectType, field); - } - } - catch (BeansException exc) { - logger.debug("Failed while inspecting " + declaredType + " for property " + field.getName() + "", exc); - } + report.missingOperation(operationType, fieldDefinition); } } } - - private boolean isConnectionType(GraphQLObjectType objectType) { - return (objectType.getName().endsWith("Connection") && - objectType.getField("edges") != null && objectType.getField("pageInfo") != null); - } - - @NotNull - private GraphQLObjectType getEdgeNodeType(GraphQLObjectType objectType) { - GraphQLList edgesListType = (GraphQLList) unwrapNonNull(objectType.getField("edges").getType()); - GraphQLObjectType edgeType = (GraphQLObjectType) edgesListType.getWrappedType(); - return (GraphQLObjectType) unwrapNonNull(edgeType.getField("node").getType()); - } - - private GraphQLType unwrapNonNull(GraphQLType outputType) { - return (outputType instanceof GraphQLNonNull wrapper ? wrapper.getWrappedType() : outputType); - } - - @Nullable - private Class unwrapPublisherTypes(ResolvableType declaredType) { - Class rawClass = declaredType.getRawClass(); - if (rawClass != null) { - ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(declaredType.getRawClass()); - if (adapter != null) { - return declaredType.getNested(2).getRawClass(); - } - } - return rawClass; - } - - private boolean isTypeAlreadyInspected(GraphQLObjectType objectType) { - return !this.seenTypes.add(objectType.getName()); - } - } + private void inspectType(GraphQLType type, ResolvableType declaredType, ReportBuilder report) { + if (type instanceof GraphQLObjectType objectType) { + inspectObjectType(objectType, declaredType, report); + } + else if (type instanceof GraphQLList listType) { + inspectType(listType.getWrappedType(), declaredType.getNested(2), report); + } + else if (type instanceof GraphQLNonNull nonNullType) { + inspectType(nonNullType.getWrappedType(), declaredType, report); + } + else if (type instanceof GraphQLNamedType namedType && logger.isTraceEnabled()){ + logger.trace("Cannot inspect type '" + namedType.getName() + "', inspector does not support " + + type.getClass().getSimpleName()); + } + } + + @SuppressWarnings("rawtypes") + private void inspectObjectType(GraphQLObjectType objectType, ResolvableType declaredType, ReportBuilder report) { + if (isTypeAlreadyInspected(objectType)) { + return; + } + if (isConnectionType(objectType)) { + objectType = getEdgeNodeType(objectType); + declaredType = declaredType.getNested(2); + } + Map typeDataFetcher = this.runtimeWiring.getDataFetcherForType(objectType.getName()); + Class declaredClass = unwrapPublisherTypes(declaredType); + for (GraphQLFieldDefinition field : objectType.getFieldDefinitions()) { + if (typeDataFetcher.containsKey(field.getName())) { + DataFetcher fieldDataFetcher = typeDataFetcher.get(field.getName()); + if (fieldDataFetcher instanceof SelfDescribingDataFetcher typedFieldDataFetcher) { + inspectType(field.getType(), typedFieldDataFetcher.getReturnType(), report); + } + } + else { + try { + if (declaredClass == null || BeanUtils.getPropertyDescriptor(declaredClass, field.getName()) == null) { + report.missingField(objectType, field); + } + } + catch (BeansException exc) { + logger.debug("Failed while inspecting " + declaredType + " for property " + field.getName() + "", exc); + } + } + } + } + + private boolean isConnectionType(GraphQLObjectType objectType) { + return (objectType.getName().endsWith("Connection") && + objectType.getField("edges") != null && objectType.getField("pageInfo") != null); + } + + @NotNull + private GraphQLObjectType getEdgeNodeType(GraphQLObjectType objectType) { + GraphQLList edgesListType = (GraphQLList) unwrapNonNull(objectType.getField("edges").getType()); + GraphQLObjectType edgeType = (GraphQLObjectType) edgesListType.getWrappedType(); + return (GraphQLObjectType) unwrapNonNull(edgeType.getField("node").getType()); + } + + private GraphQLType unwrapNonNull(GraphQLType outputType) { + return (outputType instanceof GraphQLNonNull wrapper ? wrapper.getWrappedType() : outputType); + } + + @Nullable + private Class unwrapPublisherTypes(ResolvableType declaredType) { + Class rawClass = declaredType.getRawClass(); + if (rawClass != null) { + ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(declaredType.getRawClass()); + if (adapter != null) { + return declaredType.getNested(2).getRawClass(); + } + } + return rawClass; + } + + private boolean isTypeAlreadyInspected(GraphQLObjectType objectType) { + return !this.seenTypes.add(objectType.getName()); + } + + + /** + * Inspect the schema mappings and produce a report. + * @param schema the schema to inspect + * @param runtimeWiring for {@code DataFetcher} mappings + * @return the resulting report + */ + public static Report inspect(GraphQLSchema schema, RuntimeWiring runtimeWiring) { + return new SchemaMappingInspector(schema, runtimeWiring).inspectMappings(); + } + + + record Report(MultiValueMap missingOperations, MultiValueMap missingFields) { String getSummary() { @@ -190,7 +205,7 @@ class SchemaMappingInspector { builder.append("no missing mapping."); } else { - builder.append(getDetailedReport()); + builder.append(getDetailedReport()); } return builder.toString(); } @@ -210,6 +225,7 @@ class SchemaMappingInspector { } + private static class ReportBuilder { private final MultiValueMap missingOperations = new LinkedMultiValueMap<>(); 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 25a47dc6..6d4f7115 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 @@ -492,7 +492,7 @@ class SchemaMappingInspectorTests { .toGraphQlSource() .schema(); - return new SchemaMappingInspector().inspectSchemaMappings(schema, wiringBuilder.build()); + return SchemaMappingInspector.inspect(schema, wiringBuilder.build()); } private RuntimeWiringConfigurer createRuntimeWiring(Class... handlerTypes) {