54
54
* Declares an {@link #inspect(GraphQLSchema, RuntimeWiring)} method that checks
55
55
* if schema fields are covered either by a {@link DataFetcher} registration,
56
56
* or match a Java object property. Fields that have neither are reported as
57
- * "unmapped" in the resulting {@link Report Resport }. The inspection also
57
+ * "unmapped" in the resulting {@link SchemaMappingReport }. The inspection also
58
58
* performs a reverse check for {@code DataFetcher} registrations against schema
59
59
* fields that don't exist.
60
60
*
82
82
* @author Rossen Stoyanchev
83
83
* @since 1.2.0
84
84
*/
85
- class SchemaMappingInspector {
85
+ final class SchemaMappingInspector {
86
86
87
87
private static final Log logger = LogFactory .getLog (SchemaMappingInspector .class );
88
88
@@ -91,12 +91,19 @@ class SchemaMappingInspector {
91
91
92
92
private final RuntimeWiring runtimeWiring ;
93
93
94
- private final ReportBuilder reportBuilder = new ReportBuilder ();
95
-
96
94
private final Set <String > inspectedTypes = new HashSet <>();
97
95
98
96
private final ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry .getSharedInstance ();
99
97
98
+ private final MultiValueMap <String , String > unmappedFields = new LinkedMultiValueMap <>();
99
+
100
+ private final Map <FieldCoordinates , DataFetcher <?>> unmappedDataFetchers = new LinkedHashMap <>();
101
+
102
+ private final Set <String > skippedTypes = new LinkedHashSet <>();
103
+
104
+ @ Nullable
105
+ private SchemaMappingReport report ;
106
+
100
107
101
108
private SchemaMappingInspector (GraphQLSchema schema , RuntimeWiring runtimeWiring ) {
102
109
Assert .notNull (schema , "GraphQLSchema is required" );
@@ -107,34 +114,41 @@ private SchemaMappingInspector(GraphQLSchema schema, RuntimeWiring runtimeWiring
107
114
108
115
109
116
/**
110
- * Inspect all fields, starting from Query, Mutation, and Subscription, and
111
- * working recursively down through the types they return.
112
- * @return a report with unmapped fields and skipped types.
117
+ * Perform an inspection and create a {@link SchemaMappingReport}.
118
+ * The inspection is one once only, during the first call to this method.
113
119
*/
114
- public Report inspect () {
120
+ public SchemaMappingReport getOrCreateReport () {
121
+ if (this .report == null ) {
122
+ checkSchema ();
123
+ checkDataFetcherRegistrations ();
124
+ this .report = new SchemaMappingReport (
125
+ this .unmappedFields , this .unmappedDataFetchers , this .skippedTypes );
126
+ }
127
+ return this .report ;
128
+ }
129
+
130
+ private void checkSchema () {
131
+
132
+ checkFieldsContainer (this .schema .getQueryType (), null );
115
133
116
- inspectFields (this .schema .getQueryType (), null );
117
134
if (this .schema .isSupportingMutations ()) {
118
- inspectFields (this .schema .getMutationType (), null );
135
+ checkFieldsContainer (this .schema .getMutationType (), null );
119
136
}
137
+
120
138
if (this .schema .isSupportingSubscriptions ()) {
121
- inspectFields (this .schema .getSubscriptionType (), null );
139
+ checkFieldsContainer (this .schema .getSubscriptionType (), null );
122
140
}
123
-
124
- inspectDataFetcherRegistrations ();
125
-
126
- return this .reportBuilder .build ();
127
141
}
128
142
129
143
/**
130
- * Inspect the given {@code GraphQLFieldsContainer} check against {@code DataFetcher}
131
- * registrations, or Java properties in the given {@code ResolvableType}.
132
- * @param fields the GraphQL schema type to inspect
144
+ * Check the given {@code GraphQLFieldsContainer} against {@code DataFetcher}
145
+ * registrations, or Java properties of the given {@code ResolvableType}.
146
+ * @param fields the GraphQL interface or object type to check
133
147
* @param resolvableType the Java type to match against, or {@code null} if
134
148
* not applicable such as for Query, Mutation, or Subscription
135
149
*/
136
150
@ SuppressWarnings ("rawtypes" )
137
- private void inspectFields (GraphQLFieldsContainer fields , @ Nullable ResolvableType resolvableType ) {
151
+ private void checkFieldsContainer (GraphQLFieldsContainer fields , @ Nullable ResolvableType resolvableType ) {
138
152
139
153
Map <String , DataFetcher > dataFetcherMap = this .runtimeWiring .getDataFetcherForType (fields .getName ());
140
154
@@ -143,7 +157,7 @@ private void inspectFields(GraphQLFieldsContainer fields, @Nullable ResolvableTy
143
157
if (dataFetcherMap .containsKey (fieldName )) {
144
158
DataFetcher <?> fetcher = dataFetcherMap .get (fieldName );
145
159
if (fetcher instanceof SelfDescribingDataFetcher <?> selfDescribingDataFetcher ) {
146
- inspectFieldType (
160
+ checkFieldType (
147
161
field .getType (), selfDescribingDataFetcher .getReturnType (),
148
162
(fields == this .schema .getSubscriptionType ()));
149
163
}
@@ -153,18 +167,18 @@ else if (isNotScalarOrEnumType(field.getType())) {
153
167
}
154
168
}
155
169
else if (resolvableType == null || !hasProperty (resolvableType , fieldName )) {
156
- this .reportBuilder . addUnmappedField (fields .getName (), fieldName );
170
+ this .unmappedFields . add (fields .getName (), fieldName );
157
171
}
158
172
}
159
173
}
160
174
161
175
/**
162
- * Inspect the output {@link GraphQLType} of a field.
176
+ * Check the output {@link GraphQLType} of a field against the given DataFetcher return type .
163
177
* @param outputType the field type to inspect
164
178
* @param resolvableType the expected Java return type
165
179
* @param isSubscriptionField whether this is for a subscription field
166
180
*/
167
- private void inspectFieldType (GraphQLType outputType , ResolvableType resolvableType , boolean isSubscriptionField ) {
181
+ private void checkFieldType (GraphQLType outputType , ResolvableType resolvableType , boolean isSubscriptionField ) {
168
182
169
183
// Remove GraphQL type wrappers, and nest within Java generic types
170
184
outputType = unwrapIfNonNull (outputType );
@@ -201,7 +215,7 @@ else if (outputType instanceof GraphQLList listType) {
201
215
}
202
216
203
217
// Nest within the
204
- inspectFields (fieldContainer , resolvableType );
218
+ checkFieldsContainer (fieldContainer , resolvableType );
205
219
}
206
220
207
221
private GraphQLType unwrapIfNonNull (GraphQLType type ) {
@@ -283,19 +297,19 @@ private boolean hasProperty(ResolvableType resolvableType, String fieldName) {
283
297
284
298
private void addSkippedType (GraphQLType type , Supplier <String > reason ) {
285
299
String typeName = typeNameToString (type );
286
- this .reportBuilder . addSkippedType (typeName );
300
+ this .skippedTypes . add (typeName );
287
301
if (logger .isDebugEnabled ()) {
288
302
logger .debug ("Skipped '" + typeName + "': " + reason .get ());
289
303
}
290
304
}
291
305
292
306
@ SuppressWarnings ("rawtypes" )
293
- private void inspectDataFetcherRegistrations () {
307
+ private void checkDataFetcherRegistrations () {
294
308
this .runtimeWiring .getDataFetchers ().forEach ((typeName , registrations ) ->
295
309
registrations .forEach ((fieldName , fetcher ) -> {
296
310
FieldCoordinates coordinates = FieldCoordinates .coordinates (typeName , fieldName );
297
311
if (this .schema .getFieldDefinition (coordinates ) == null ) {
298
- this .reportBuilder . addUnmappedDataFetcher (coordinates , fetcher );
312
+ this .unmappedDataFetchers . put (coordinates , fetcher );
299
313
}
300
314
}));
301
315
}
@@ -307,73 +321,9 @@ private void inspectDataFetcherRegistrations() {
307
321
* @param runtimeWiring for {@code DataFetcher} registrations
308
322
* @return the created report
309
323
*/
310
- public static Report inspect (GraphQLSchema schema , RuntimeWiring runtimeWiring ) {
311
- SchemaMappingInspector inspector = new SchemaMappingInspector (schema , runtimeWiring );
312
- return inspector .inspect ();
324
+ public static SchemaMappingReport inspect (GraphQLSchema schema , RuntimeWiring runtimeWiring ) {
325
+ return new SchemaMappingInspector (schema , runtimeWiring ).getOrCreateReport ();
313
326
}
314
327
315
328
316
-
317
- /**
318
- * The report produced as a result of schema mappings inspection.
319
- * @param unmappedFields map with type names as keys, and unmapped field names as values
320
- * @param unmappedDataFetchers map with unmapped {@code DataFetcher}s and their field coordinates
321
- * @param skippedTypes the names of types skipped by the inspection
322
- */
323
- public record Report (
324
- MultiValueMap <String , String > unmappedFields ,
325
- Map <FieldCoordinates , DataFetcher <?>> unmappedDataFetchers ,
326
- Set <String > skippedTypes ) {
327
-
328
- @ Override
329
- public String toString () {
330
- return "GraphQL schema inspection:\n " +
331
- "\t Unmapped fields: " + this .unmappedFields + "\n " +
332
- "\t Unmapped DataFetcher registrations: " + this .unmappedDataFetchers + "\n " +
333
- "\t Skipped types: " + this .skippedTypes ;
334
- }
335
- }
336
-
337
-
338
- /**
339
- * Builder for a {@link Report}.
340
- */
341
- private static class ReportBuilder {
342
-
343
- private final MultiValueMap <String , String > unmappedFields = new LinkedMultiValueMap <>();
344
-
345
- private final Map <FieldCoordinates , DataFetcher <?>> unmappedDataFetchers = new LinkedHashMap <>();
346
-
347
- private final Set <String > skippedTypes = new LinkedHashSet <>();
348
-
349
- /**
350
- * Add an unmapped field.
351
- */
352
- public void addUnmappedField (String typeName , String fieldName ) {
353
- this .unmappedFields .add (typeName , fieldName );
354
- }
355
-
356
- /**
357
- * Add an unmapped {@code DataFetcher} registration.
358
- */
359
- public void addUnmappedDataFetcher (FieldCoordinates coordinates , DataFetcher <?> dataFetcher ) {
360
- this .unmappedDataFetchers .put (coordinates , dataFetcher );
361
- }
362
-
363
- /**
364
- * Add a skipped type name.
365
- */
366
- public void addSkippedType (String typeName ) {
367
- this .skippedTypes .add (typeName );
368
- }
369
-
370
- public Report build () {
371
- return new Report (
372
- new LinkedMultiValueMap <>(this .unmappedFields ),
373
- new LinkedHashMap <>(this .unmappedDataFetchers ),
374
- new LinkedHashSet <>(this .skippedTypes ));
375
- }
376
-
377
- }
378
-
379
329
}
0 commit comments