17
17
package org .springframework .graphql .execution ;
18
18
19
19
import java .util .HashSet ;
20
+ import java .util .LinkedHashMap ;
20
21
import java .util .LinkedHashSet ;
21
22
import java .util .Map ;
22
23
import java .util .Set ;
23
24
24
25
import graphql .schema .DataFetcher ;
26
+ import graphql .schema .FieldCoordinates ;
25
27
import graphql .schema .GraphQLEnumType ;
26
28
import graphql .schema .GraphQLFieldDefinition ;
27
29
import graphql .schema .GraphQLFieldsContainer ;
51
53
* Declares an {@link #inspect(GraphQLSchema, RuntimeWiring)} method that checks
52
54
* if schema fields are covered either by a {@link DataFetcher} registration,
53
55
* or match a Java object property. Fields that have neither are reported as
54
- * "unmapped" in the resulting {@link Report Resport}.
56
+ * "unmapped" in the resulting {@link Report Resport}. The inspection also
57
+ * performs a reverse check for {@code DataFetcher} registrations against schema
58
+ * fields that don't exist.
55
59
*
56
- * <p>The inspection depends on {@code DataFetcher}s to be
60
+ * <p>The schema field inspection depends on {@code DataFetcher}s to be
57
61
* {@link SelfDescribingDataFetcher} to be able to compare schema type and Java
58
62
* object type structure. If a {@code DataFetcher} does not implement this
59
63
* interface, then the Java type remains unknown, and the field type is reported
@@ -108,21 +112,23 @@ private SchemaMappingInspector(GraphQLSchema schema, RuntimeWiring runtimeWiring
108
112
*/
109
113
public Report inspect () {
110
114
111
- inspectType (this .schema .getQueryType (), null );
115
+ inspectSchemaType (this .schema .getQueryType (), null );
112
116
113
117
if (this .schema .isSupportingMutations ()) {
114
- inspectType (this .schema .getMutationType (), null );
118
+ inspectSchemaType (this .schema .getMutationType (), null );
115
119
}
116
120
117
121
if (this .schema .isSupportingSubscriptions ()) {
118
- inspectType (this .schema .getSubscriptionType (), null );
122
+ inspectSchemaType (this .schema .getSubscriptionType (), null );
119
123
}
120
124
125
+ inspectDataFetcherRegistrations ();
126
+
121
127
return this .reportBuilder .build ();
122
128
}
123
129
124
130
@ SuppressWarnings ("rawtypes" )
125
- private void inspectType (GraphQLType type , @ Nullable ResolvableType resolvableType ) {
131
+ private void inspectSchemaType (GraphQLType type , @ Nullable ResolvableType resolvableType ) {
126
132
Assert .notNull (type , "No GraphQLType" );
127
133
128
134
type = unwrapNonNull (type );
@@ -166,9 +172,9 @@ else if (resolvableType != null && resolveClassToCompare(resolvableType) == Obje
166
172
for (GraphQLFieldDefinition field : fieldContainer .getFieldDefinitions ()) {
167
173
String fieldName = field .getName ();
168
174
if (dataFetcherMap .containsKey (fieldName )) {
169
- DataFetcher fetcher = dataFetcherMap .get (fieldName );
175
+ DataFetcher <?> fetcher = dataFetcherMap .get (fieldName );
170
176
if (fetcher instanceof SelfDescribingDataFetcher <?> selfDescribingDataFetcher ) {
171
- inspectType (field .getType (), selfDescribingDataFetcher .getReturnType ());
177
+ inspectSchemaType (field .getType (), selfDescribingDataFetcher .getReturnType ());
172
178
}
173
179
else if (isNotScalarOrEnumType (field .getType ())) {
174
180
if (logger .isDebugEnabled ()) {
@@ -233,6 +239,17 @@ private Class<?> resolveClassToCompare(ResolvableType resolvableType) {
233
239
return (adapter != null ? resolvableType .getNested (2 ).resolve (Object .class ) : clazz );
234
240
}
235
241
242
+ @ SuppressWarnings ("rawtypes" )
243
+ private void inspectDataFetcherRegistrations () {
244
+ this .runtimeWiring .getDataFetchers ().forEach ((typeName , registrations ) ->
245
+ registrations .forEach ((fieldName , fetcher ) -> {
246
+ FieldCoordinates coordinates = FieldCoordinates .coordinates (typeName , fieldName );
247
+ if (this .schema .getFieldDefinition (coordinates ) == null ) {
248
+ this .reportBuilder .addUnmappedDataFetcher (coordinates , fetcher );
249
+ }
250
+ }));
251
+ }
252
+
236
253
237
254
/**
238
255
* Check the schema against {@code DataFetcher} registrations, and produce a report.
@@ -249,15 +266,20 @@ public static Report inspect(GraphQLSchema schema, RuntimeWiring runtimeWiring)
249
266
250
267
/**
251
268
* The report produced as a result of schema mappings inspection.
252
- * @param unmappedFields a map with type names as keys, and unmapped field names as values
269
+ * @param unmappedFields map with type names as keys, and unmapped field names as values
270
+ * @param unmappedDataFetchers map with unmapped {@code DataFetcher}s and their field coordinates
253
271
* @param skippedTypes the names of types skipped by the inspection
254
272
*/
255
- public record Report (MultiValueMap <String , String > unmappedFields , Set <String > skippedTypes ) {
273
+ public record Report (
274
+ MultiValueMap <String , String > unmappedFields ,
275
+ Map <FieldCoordinates , DataFetcher <?>> unmappedDataFetchers ,
276
+ Set <String > skippedTypes ) {
256
277
257
278
@ Override
258
279
public String toString () {
259
280
return "GraphQL schema inspection:\n " +
260
281
"\t Unmapped fields: " + this .unmappedFields + "\n " +
282
+ "\t Unmapped DataFetcher registrations: " + this .unmappedDataFetchers + "\n " +
261
283
"\t Skipped types: " + this .skippedTypes ;
262
284
}
263
285
}
@@ -270,6 +292,8 @@ private static class ReportBuilder {
270
292
271
293
private final MultiValueMap <String , String > unmappedFields = new LinkedMultiValueMap <>();
272
294
295
+ private final Map <FieldCoordinates , DataFetcher <?>> unmappedDataFetchers = new LinkedHashMap <>();
296
+
273
297
private final Set <String > skippedTypes = new LinkedHashSet <>();
274
298
275
299
/**
@@ -279,6 +303,13 @@ public void addUnmappedField(String typeName, String fieldName) {
279
303
this .unmappedFields .add (typeName , fieldName );
280
304
}
281
305
306
+ /**
307
+ * Add an unmapped {@code DataFetcher} registration.
308
+ */
309
+ public void addUnmappedDataFetcher (FieldCoordinates coordinates , DataFetcher <?> dataFetcher ) {
310
+ this .unmappedDataFetchers .put (coordinates , dataFetcher );
311
+ }
312
+
282
313
/**
283
314
* Add a skipped type name.
284
315
*/
@@ -289,6 +320,7 @@ public void addSkippedType(String typeName) {
289
320
public Report build () {
290
321
return new Report (
291
322
new LinkedMultiValueMap <>(this .unmappedFields ),
323
+ new LinkedHashMap <>(this .unmappedDataFetchers ),
292
324
new LinkedHashSet <>(this .skippedTypes ));
293
325
}
294
326
0 commit comments