1
1
package graphql .schema .idl ;
2
2
3
3
import graphql .GraphQLError ;
4
+ import graphql .language .AstPrinter ;
4
5
import graphql .language .FieldDefinition ;
5
6
import graphql .language .InputObjectTypeDefinition ;
6
7
import graphql .language .InputValueDefinition ;
13
14
import graphql .language .TypeExtensionDefinition ;
14
15
import graphql .language .TypeName ;
15
16
import graphql .language .UnionTypeDefinition ;
17
+ import graphql .schema .idl .errors .MissingInterfaceTypeError ;
16
18
import graphql .schema .idl .errors .MissingScalarImplementationError ;
17
19
import graphql .schema .idl .errors .MissingTypeError ;
18
20
import graphql .schema .idl .errors .MissingTypeResolverError ;
19
21
import graphql .schema .idl .errors .OperationTypesMustBeObjects ;
20
22
import graphql .schema .idl .errors .QueryOperationMissingError ;
21
23
import graphql .schema .idl .errors .SchemaMissingError ;
22
24
import graphql .schema .idl .errors .SchemaProblem ;
25
+ import graphql .schema .idl .errors .TypeExtensionFieldRedefinitionError ;
26
+ import graphql .schema .idl .errors .TypeExtensionMissingBaseTypeError ;
23
27
24
28
import java .util .ArrayList ;
25
29
import java .util .Collection ;
26
30
import java .util .List ;
27
31
import java .util .Map ;
28
32
import java .util .Optional ;
29
33
import java .util .function .Consumer ;
34
+ import java .util .function .Function ;
30
35
import java .util .stream .Collectors ;
31
36
32
37
/**
@@ -39,13 +44,16 @@ public class SchemaTypeChecker {
39
44
public List <GraphQLError > checkTypeRegistry (TypeDefinitionRegistry typeRegistry , RuntimeWiring wiring ) throws SchemaProblem {
40
45
List <GraphQLError > errors = new ArrayList <>();
41
46
checkForMissingTypes (errors , typeRegistry );
47
+
48
+ checkTypeExtensionsHaveCorrespondingType (errors , typeRegistry );
49
+ checkTypeExtensionsFieldRedefinition (errors , typeRegistry );
50
+
42
51
checkSchemaInvariants (errors , typeRegistry );
43
52
44
53
checkScalarImplementationsArePresent (errors , typeRegistry , wiring );
45
54
checkTypeResolversArePresent (errors , typeRegistry , wiring );
46
55
47
56
return errors ;
48
-
49
57
}
50
58
51
59
private void checkSchemaInvariants (List <GraphQLError > errors , TypeDefinitionRegistry typeRegistry ) {
@@ -73,11 +81,11 @@ private void checkSchemaInvariants(List<GraphQLError> errors, TypeDefinitionRegi
73
81
74
82
private void checkForMissingTypes (List <GraphQLError > errors , TypeDefinitionRegistry typeRegistry ) {
75
83
// type extensions
76
- Collection <TypeExtensionDefinition > typeExtensions = typeRegistry .typeExtensions ().values ();
84
+ List <TypeExtensionDefinition > typeExtensions = typeRegistry .typeExtensions ().values (). stream (). flatMap ( Collection :: stream ). collect ( Collectors . toList () );
77
85
typeExtensions .forEach (typeExtension -> {
78
86
79
87
List <Type > implementsTypes = typeExtension .getImplements ();
80
- implementsTypes .forEach (checkTypeExists ( "type extension" , typeRegistry , errors , typeExtension ));
88
+ implementsTypes .forEach (checkInterfaceTypeExists ( typeRegistry , errors , typeExtension ));
81
89
82
90
checkFieldTypesPresent (typeRegistry , errors , typeExtension , typeExtension .getFieldDefinitions ());
83
91
@@ -91,7 +99,7 @@ private void checkForMissingTypes(List<GraphQLError> errors, TypeDefinitionRegis
91
99
objectTypes .forEach (objectType -> {
92
100
93
101
List <Type > implementsTypes = objectType .getImplements ();
94
- implementsTypes .forEach (checkTypeExists ( "object" , typeRegistry , errors , objectType ));
102
+ implementsTypes .forEach (checkInterfaceTypeExists ( typeRegistry , errors , objectType ));
95
103
96
104
checkFieldTypesPresent (typeRegistry , errors , objectType , objectType .getFieldDefinitions ());
97
105
@@ -128,6 +136,7 @@ private void checkForMissingTypes(List<GraphQLError> errors, TypeDefinitionRegis
128
136
});
129
137
}
130
138
139
+
131
140
private void checkScalarImplementationsArePresent (List <GraphQLError > errors , TypeDefinitionRegistry typeRegistry , RuntimeWiring wiring ) {
132
141
typeRegistry .scalars ().keySet ().forEach (scalarName -> {
133
142
if (!wiring .getScalars ().containsKey (scalarName )) {
@@ -175,6 +184,88 @@ private Consumer<Type> checkTypeExists(String typeOfType, TypeDefinitionRegistry
175
184
};
176
185
}
177
186
187
+ private Consumer <? super Type > checkInterfaceTypeExists (TypeDefinitionRegistry typeRegistry , List <GraphQLError > errors , TypeDefinition typeDefinition ) {
188
+ return t -> {
189
+ TypeInfo typeInfo = TypeInfo .typeInfo (t );
190
+ TypeName unwrapped = typeInfo .getTypeName ();
191
+ Optional <TypeDefinition > type = typeRegistry .getType (unwrapped );
192
+ if (!type .isPresent ()) {
193
+ errors .add (new MissingInterfaceTypeError ("interface" , typeDefinition , unwrapped ));
194
+ } else if (!(type .get () instanceof InterfaceTypeDefinition )) {
195
+ errors .add (new MissingInterfaceTypeError ("interface" , typeDefinition , unwrapped ));
196
+ }
197
+ };
198
+ }
199
+
200
+ /*
201
+ A type can re-define a field if its actual the same type, but if they make 'fieldA : String' into
202
+ 'fieldA : Int' then we cant handle that. Even 'fieldA : String' to 'fieldA: String!' is tough to handle
203
+ so we don't
204
+ */
205
+ private void checkTypeExtensionsFieldRedefinition (List <GraphQLError > errors , TypeDefinitionRegistry typeRegistry ) {
206
+ Map <String , List <TypeExtensionDefinition >> typeExtensions = typeRegistry .typeExtensions ();
207
+ typeExtensions .values ().forEach (extList -> extList .forEach (typeExtension -> {
208
+ //
209
+ // first check for field re-defs within a type ext
210
+ for (TypeExtensionDefinition otherTypeExt : extList ) {
211
+ if (otherTypeExt == typeExtension ) {
212
+ continue ;
213
+ }
214
+ // its the children that matter - the fields cannot be redefined
215
+ checkForFieldRedefinition (errors , otherTypeExt , otherTypeExt .getFieldDefinitions (), typeExtension .getFieldDefinitions ());
216
+ }
217
+ //
218
+ // then check for field re-defs from the base type
219
+ Optional <TypeDefinition > type = typeRegistry .getType (typeExtension .getName ());
220
+ if (type .isPresent () && type .get () instanceof ObjectTypeDefinition ) {
221
+ ObjectTypeDefinition baseType = (ObjectTypeDefinition ) type .get ();
222
+
223
+ checkForFieldRedefinition (errors , typeExtension , typeExtension .getFieldDefinitions (), baseType .getFieldDefinitions ());
224
+ }
225
+
226
+ }));
227
+
228
+ }
229
+
230
+ private void checkForFieldRedefinition (List <GraphQLError > errors , TypeDefinition typeDefinition , List <FieldDefinition > fieldDefinitions , List <FieldDefinition > referenceFieldDefinitions ) {
231
+ Map <String , FieldDefinition > referenceFields = referenceFieldDefinitions .stream ()
232
+ .collect (Collectors .toMap (
233
+ FieldDefinition ::getName , Function .identity ()
234
+ ));
235
+
236
+ fieldDefinitions .forEach (fld -> {
237
+ FieldDefinition referenceField = referenceFields .get (fld .getName ());
238
+ if (referenceFields .containsKey (fld .getName ())) {
239
+ // ok they have the same field but is it the same type
240
+ if (!isSameType (fld .getType (), referenceField .getType ())) {
241
+ errors .add (new TypeExtensionFieldRedefinitionError (typeDefinition , fld ));
242
+ }
243
+ }
244
+ });
245
+ }
246
+
247
+ private boolean isSameType (Type type1 , Type type2 ) {
248
+ String s1 = AstPrinter .printAst (type1 );
249
+ String s2 = AstPrinter .printAst (type2 );
250
+ return s1 .equals (s2 );
251
+ }
252
+
253
+ private void checkTypeExtensionsHaveCorrespondingType (List <GraphQLError > errors , TypeDefinitionRegistry typeRegistry ) {
254
+ Map <String , List <TypeExtensionDefinition >> typeExtensions = typeRegistry .typeExtensions ();
255
+ typeExtensions .forEach ((name , extTypeList ) -> {
256
+ TypeExtensionDefinition extensionDefinition = extTypeList .get (0 );
257
+ Optional <TypeDefinition > typeDefinition = typeRegistry .getType (new TypeName (name ));
258
+ if (!typeDefinition .isPresent ()) {
259
+ errors .add (new TypeExtensionMissingBaseTypeError (extensionDefinition ));
260
+ } else {
261
+ if (!(typeDefinition .get () instanceof ObjectTypeDefinition )) {
262
+ errors .add (new TypeExtensionMissingBaseTypeError (extensionDefinition ));
263
+ }
264
+ }
265
+ });
266
+ }
267
+
268
+
178
269
private Consumer <OperationTypeDefinition > checkOperationTypesExist (TypeDefinitionRegistry typeRegistry , List <GraphQLError > errors ) {
179
270
return op -> {
180
271
TypeName unwrapped = TypeInfo .typeInfo (op .getType ()).getTypeName ();
0 commit comments