49
49
import org .springframework .core .ResolvableType ;
50
50
import org .springframework .lang .Nullable ;
51
51
import org .springframework .util .Assert ;
52
+ import org .springframework .util .CollectionUtils ;
52
53
import org .springframework .util .LinkedMultiValueMap ;
53
54
import org .springframework .util .MultiValueMap ;
54
55
@@ -135,13 +136,13 @@ private void checkSchemaFields() {
135
136
}
136
137
137
138
/**
138
- * Check the given {@code GraphQLFieldsContainer} against {@code DataFetcher}
139
- * registrations, or Java properties of the given {@code ResolvableType}.
140
- * @param fieldContainer the GraphQL interface or object type to check
141
- * @param resolvableType the Java type to match against, or {@code null} if
142
- * not applicable such as for Query, Mutation, or Subscription
139
+ * Check fields of the given {@code GraphQLFieldsContainer} to make sure there
140
+ * is either a {@code DataFetcher} registration, or a corresponding property
141
+ * in the given Java type, which may be {@code null} for the top-level types
142
+ * Query, Mutation, and Subscription.
143
143
*/
144
- private void checkFieldsContainer (GraphQLFieldsContainer fieldContainer , @ Nullable ResolvableType resolvableType ) {
144
+ private void checkFieldsContainer (
145
+ GraphQLFieldsContainer fieldContainer , @ Nullable ResolvableType resolvableType ) {
145
146
146
147
String typeName = fieldContainer .getName ();
147
148
Map <String , DataFetcher > dataFetcherMap = this .dataFetchers .getOrDefault (typeName , Collections .emptyMap ());
@@ -159,16 +160,21 @@ else if (resolvableType == null || !hasProperty(resolvableType, fieldName)) {
159
160
}
160
161
161
162
/**
162
- * Check the output {@link GraphQLType} of a field against the given DataFetcher return type.
163
- * @param parent the parent of the field
164
- * @param field the field to inspect
165
- * @param dataFetcher the registered DataFetcher
163
+ * Perform the following:
164
+ * <ul>
165
+ * <li>Resolve the field type and the {@code DataFetcher} return type, and recurse
166
+ * with {@link #checkFieldsContainer} if there is sufficient type information.
167
+ * <li>Resolve the arguments the {@code DataFetcher} depends on and check they
168
+ * are defined in the schema.
169
+ * </ul>
166
170
*/
167
- private void checkField (GraphQLFieldsContainer parent , GraphQLFieldDefinition field , DataFetcher <?> dataFetcher ) {
171
+ private void checkField (
172
+ GraphQLFieldsContainer parent , GraphQLFieldDefinition field , DataFetcher <?> dataFetcher ) {
168
173
169
174
ResolvableType resolvableType = ResolvableType .NONE ;
170
- if (dataFetcher instanceof SelfDescribingDataFetcher <?> selfDescribingDataFetcher ) {
171
- resolvableType = selfDescribingDataFetcher .getReturnType ();
175
+ if (dataFetcher instanceof SelfDescribingDataFetcher <?> selfDescribing ) {
176
+ resolvableType = selfDescribing .getReturnType ();
177
+ checkFieldArguments (field , selfDescribing );
172
178
}
173
179
174
180
// Remove GraphQL type wrappers, and nest within Java generic types
@@ -210,6 +216,17 @@ else if (outputType instanceof GraphQLList listType) {
210
216
checkFieldsContainer (fieldContainer , resolvableType );
211
217
}
212
218
219
+ private void checkFieldArguments (GraphQLFieldDefinition field , SelfDescribingDataFetcher <?> dataFetcher ) {
220
+
221
+ List <String > arguments = dataFetcher .getArguments ().keySet ().stream ()
222
+ .filter (name -> field .getArgument (name ) == null )
223
+ .toList ();
224
+
225
+ if (!arguments .isEmpty ()) {
226
+ this .reportBuilder .unmappedArgument (dataFetcher , arguments );
227
+ }
228
+ }
229
+
213
230
private GraphQLType unwrapIfNonNull (GraphQLType type ) {
214
231
return (type instanceof GraphQLNonNull graphQLNonNull ? graphQLNonNull .getWrappedType () : type );
215
232
}
@@ -347,6 +364,8 @@ private class ReportBuilder {
347
364
348
365
private final Map <FieldCoordinates , DataFetcher <?>> unmappedRegistrations = new LinkedHashMap <>();
349
366
367
+ private final MultiValueMap <DataFetcher <?>, String > unmappedArguments = new LinkedMultiValueMap <>();
368
+
350
369
private final List <SchemaReport .SkippedType > skippedTypes = new ArrayList <>();
351
370
352
371
public void unmappedField (FieldCoordinates coordinates ) {
@@ -357,12 +376,17 @@ public void unmappedRegistration(FieldCoordinates coordinates, DataFetcher<?> da
357
376
this .unmappedRegistrations .put (coordinates , dataFetcher );
358
377
}
359
378
379
+ public void unmappedArgument (DataFetcher <?> dataFetcher , List <String > arguments ) {
380
+ this .unmappedArguments .put (dataFetcher , arguments );
381
+ }
382
+
360
383
public void skippedType (GraphQLType type , FieldCoordinates coordinates ) {
361
384
this .skippedTypes .add (new DefaultSkippedType (type , coordinates ));
362
385
}
363
386
364
387
public SchemaReport build () {
365
- return new DefaultSchemaReport (this .unmappedFields , this .unmappedRegistrations , this .skippedTypes );
388
+ return new DefaultSchemaReport (
389
+ this .unmappedFields , this .unmappedRegistrations , this .unmappedArguments , this .skippedTypes );
366
390
}
367
391
368
392
}
@@ -377,14 +401,17 @@ private class DefaultSchemaReport implements SchemaReport {
377
401
378
402
private final Map <FieldCoordinates , DataFetcher <?>> unmappedRegistrations ;
379
403
404
+ private final MultiValueMap <DataFetcher <?>, String > unmappedArguments ;
405
+
380
406
private final List <SchemaReport .SkippedType > skippedTypes ;
381
407
382
408
public DefaultSchemaReport (
383
409
List <FieldCoordinates > unmappedFields , Map <FieldCoordinates , DataFetcher <?>> unmappedRegistrations ,
384
- List <SkippedType > skippedTypes ) {
410
+ MultiValueMap < DataFetcher <?>, String > unmappedArguments , List <SkippedType > skippedTypes ) {
385
411
386
412
this .unmappedFields = Collections .unmodifiableList (unmappedFields );
387
413
this .unmappedRegistrations = Collections .unmodifiableMap (unmappedRegistrations );
414
+ this .unmappedArguments = CollectionUtils .unmodifiableMultiValueMap (unmappedArguments );
388
415
this .skippedTypes = Collections .unmodifiableList (skippedTypes );
389
416
}
390
417
@@ -398,6 +425,11 @@ public Map<FieldCoordinates, DataFetcher<?>> unmappedRegistrations() {
398
425
return this .unmappedRegistrations ;
399
426
}
400
427
428
+ @ Override
429
+ public MultiValueMap <DataFetcher <?>, String > unmappedArguments () {
430
+ return this .unmappedArguments ;
431
+ }
432
+
401
433
@ Override
402
434
public List <SkippedType > skippedTypes () {
403
435
return this .skippedTypes ;
@@ -421,6 +453,7 @@ public String toString() {
421
453
return "GraphQL schema inspection:\n " +
422
454
"\t Unmapped fields: " + formatUnmappedFields () + "\n " +
423
455
"\t Unmapped registrations: " + this .unmappedRegistrations + "\n " +
456
+ "\t Unmapped arguments: " + this .unmappedArguments + "\n " +
424
457
"\t Skipped types: " + this .skippedTypes ;
425
458
}
426
459
0 commit comments