@@ -1245,16 +1245,53 @@ func sameType(typeA, typeB Type) bool {
1245
1245
return false
1246
1246
}
1247
1247
1248
+ // Two types conflict if both types could not apply to a value simultaneously.
1249
+ // Composite types are ignored as their individual field types will be compared
1250
+ // later recursively. However List and Non-Null types must match.
1251
+ func doTypesConflict (type1 Output , type2 Output ) bool {
1252
+ if type1 , ok := type1 .(* List ); ok {
1253
+ if type2 , ok := type2 .(* List ); ok {
1254
+ return doTypesConflict (type1 .OfType , type2 .OfType )
1255
+ }
1256
+ return true
1257
+ }
1258
+ if type2 , ok := type2 .(* List ); ok {
1259
+ if type1 , ok := type1 .(* List ); ok {
1260
+ return doTypesConflict (type1 .OfType , type2 .OfType )
1261
+ }
1262
+ return true
1263
+ }
1264
+ if type1 , ok := type1 .(* NonNull ); ok {
1265
+ if type2 , ok := type2 .(* NonNull ); ok {
1266
+ return doTypesConflict (type1 .OfType , type2 .OfType )
1267
+ }
1268
+ return true
1269
+ }
1270
+ if type2 , ok := type2 .(* NonNull ); ok {
1271
+ if type1 , ok := type1 .(* NonNull ); ok {
1272
+ return doTypesConflict (type1 .OfType , type2 .OfType )
1273
+ }
1274
+ return true
1275
+ }
1276
+ if IsLeafType (type1 ) || IsLeafType (type2 ) {
1277
+ return type1 != type2
1278
+ }
1279
+ return false
1280
+ }
1281
+
1248
1282
// OverlappingFieldsCanBeMergedRule Overlapping fields can be merged
1249
1283
//
1250
1284
// A selection set is only valid if all fields (including spreading any
1251
1285
// fragments) either correspond to distinct response names or can be merged
1252
1286
// without ambiguity.
1253
1287
func OverlappingFieldsCanBeMergedRule (context * ValidationContext ) * ValidationRuleInstance {
1254
1288
1289
+ var getSubfieldMap func (ast1 * ast.Field , type1 Output , ast2 * ast.Field , type2 Output ) map [string ][]* fieldDefPair
1290
+ var subfieldConflicts func (conflicts []* conflict , responseName string , ast1 * ast.Field , ast2 * ast.Field ) * conflict
1291
+ var findConflicts func (parentFieldsAreMutuallyExclusive bool , fieldMap map [string ][]* fieldDefPair ) (conflicts []* conflict )
1292
+
1255
1293
comparedSet := newPairSet ()
1256
- var findConflicts func (fieldMap map [string ][]* fieldDefPair ) (conflicts []* conflict )
1257
- findConflict := func (responseName string , field * fieldDefPair , field2 * fieldDefPair ) * conflict {
1294
+ findConflict := func (parentFieldsAreMutuallyExclusive bool , responseName string , field * fieldDefPair , field2 * fieldDefPair ) * conflict {
1258
1295
1259
1296
parentType1 := field .ParentType
1260
1297
ast1 := field .Field
@@ -1269,46 +1306,21 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul
1269
1306
return nil
1270
1307
}
1271
1308
1272
- // If the statically known parent types could not possibly apply at the same
1273
- // time, then it is safe to permit them to diverge as they will not present
1274
- // any ambiguity by differing.
1275
- // It is known that two parent types could never overlap if they are
1276
- // different Object types. Interface or Union types might overlap - if not
1277
- // in the current state of the schema, then perhaps in some future version,
1278
- // thus may not safely diverge.
1279
- if parentType1 != parentType2 {
1280
- _ , ok1 := parentType1 .(* Object )
1281
- _ , ok2 := parentType2 .(* Object )
1282
- if ok1 && ok2 {
1283
- return nil
1284
- }
1285
- }
1286
-
1287
1309
// Memoize, do not report the same issue twice.
1310
+ // Note: Two overlapping ASTs could be encountered both when
1311
+ // `parentFieldsAreMutuallyExclusive` is true and is false, which could
1312
+ // produce different results (when `true` being a subset of `false`).
1313
+ // However we do not need to include this piece of information when
1314
+ // memoizing since this rule visits leaf fields before their parent fields,
1315
+ // ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first
1316
+ // time two overlapping fields are encountered, ensuring that the full
1317
+ // set of validation rules are always checked when necessary.
1288
1318
if comparedSet .Has (ast1 , ast2 ) {
1289
1319
return nil
1290
1320
}
1291
1321
comparedSet .Add (ast1 , ast2 )
1292
1322
1293
- name1 := ""
1294
- if ast1 .Name != nil {
1295
- name1 = ast1 .Name .Value
1296
- }
1297
- name2 := ""
1298
- if ast2 .Name != nil {
1299
- name2 = ast2 .Name .Value
1300
- }
1301
- if name1 != name2 {
1302
- return & conflict {
1303
- Reason : conflictReason {
1304
- Name : responseName ,
1305
- Message : fmt .Sprintf (`%v and %v are different fields` , name1 , name2 ),
1306
- },
1307
- FieldsLeft : []ast.Node {ast1 },
1308
- FieldsRight : []ast.Node {ast2 },
1309
- }
1310
- }
1311
-
1323
+ // The return type for each field.
1312
1324
var type1 Type
1313
1325
var type2 Type
1314
1326
if def1 != nil {
@@ -1318,27 +1330,74 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul
1318
1330
type2 = def2 .Type
1319
1331
}
1320
1332
1321
- if type1 != nil && type2 != nil && ! sameType (type1 , type2 ) {
1322
- return & conflict {
1323
- Reason : conflictReason {
1324
- Name : responseName ,
1325
- Message : fmt .Sprintf (`they return differing types %v and %v` , type1 , type2 ),
1326
- },
1327
- FieldsLeft : []ast.Node {ast1 },
1328
- FieldsRight : []ast.Node {ast2 },
1333
+ // If it is known that two fields could not possibly apply at the same
1334
+ // time, due to the parent types, then it is safe to permit them to diverge
1335
+ // in aliased field or arguments used as they will not present any ambiguity
1336
+ // by differing.
1337
+ // It is known that two parent types could never overlap if they are
1338
+ // different Object types. Interface or Union types might overlap - if not
1339
+ // in the current state of the schema, then perhaps in some future version,
1340
+ // thus may not safely diverge.
1341
+ _ , isParentType1Object := parentType1 .(* Object )
1342
+ _ , isParentType2Object := parentType2 .(* Object )
1343
+ fieldsAreMutuallyExclusive := parentFieldsAreMutuallyExclusive || parentType1 != parentType2 && isParentType1Object && isParentType2Object
1344
+
1345
+ if ! fieldsAreMutuallyExclusive {
1346
+ // Two aliases must refer to the same field.
1347
+ name1 := ""
1348
+ name2 := ""
1349
+
1350
+ if ast1 .Name != nil {
1351
+ name1 = ast1 .Name .Value
1352
+ }
1353
+ if ast2 .Name != nil {
1354
+ name2 = ast2 .Name .Value
1355
+ }
1356
+ if name1 != name2 {
1357
+ return & conflict {
1358
+ Reason : conflictReason {
1359
+ Name : responseName ,
1360
+ Message : fmt .Sprintf (`%v and %v are different fields` , name1 , name2 ),
1361
+ },
1362
+ FieldsLeft : []ast.Node {ast1 },
1363
+ FieldsRight : []ast.Node {ast2 },
1364
+ }
1365
+ }
1366
+
1367
+ // Two field calls must have the same arguments.
1368
+ if ! sameArguments (ast1 .Arguments , ast2 .Arguments ) {
1369
+ return & conflict {
1370
+ Reason : conflictReason {
1371
+ Name : responseName ,
1372
+ Message : `they have differing arguments` ,
1373
+ },
1374
+ FieldsLeft : []ast.Node {ast1 },
1375
+ FieldsRight : []ast.Node {ast2 },
1376
+ }
1329
1377
}
1330
1378
}
1331
- if ! sameArguments (ast1 .Arguments , ast2 .Arguments ) {
1379
+
1380
+ if type1 != nil && type2 != nil && doTypesConflict (type1 , type2 ) {
1332
1381
return & conflict {
1333
1382
Reason : conflictReason {
1334
1383
Name : responseName ,
1335
- Message : `they have differing arguments` ,
1384
+ Message : fmt . Sprintf ( `they return conflicting types %v and %v` , type1 , type2 ) ,
1336
1385
},
1337
1386
FieldsLeft : []ast.Node {ast1 },
1338
1387
FieldsRight : []ast.Node {ast2 },
1339
1388
}
1340
1389
}
1341
1390
1391
+ subFieldMap := getSubfieldMap (ast1 , type1 , ast2 , type2 )
1392
+ if subFieldMap != nil {
1393
+ conflicts := findConflicts (fieldsAreMutuallyExclusive , subFieldMap )
1394
+ return subfieldConflicts (conflicts , responseName , ast1 , ast2 )
1395
+ }
1396
+
1397
+ return nil
1398
+ }
1399
+
1400
+ getSubfieldMap = func (ast1 * ast.Field , type1 Output , ast2 * ast.Field , type2 Output ) map [string ][]* fieldDefPair {
1342
1401
selectionSet1 := ast1 .SelectionSet
1343
1402
selectionSet2 := ast2 .SelectionSet
1344
1403
if selectionSet1 != nil && selectionSet2 != nil {
@@ -1357,32 +1416,34 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul
1357
1416
visitedFragmentNames ,
1358
1417
subfieldMap ,
1359
1418
)
1360
- conflicts := findConflicts (subfieldMap )
1361
- if len (conflicts ) > 0 {
1362
-
1363
- conflictReasons := []conflictReason {}
1364
- conflictFieldsLeft := []ast.Node {ast1 }
1365
- conflictFieldsRight := []ast.Node {ast2 }
1366
- for _ , c := range conflicts {
1367
- conflictReasons = append (conflictReasons , c .Reason )
1368
- conflictFieldsLeft = append (conflictFieldsLeft , c .FieldsLeft ... )
1369
- conflictFieldsRight = append (conflictFieldsRight , c .FieldsRight ... )
1370
- }
1419
+ return subfieldMap
1420
+ }
1421
+ return nil
1422
+ }
1371
1423
1372
- return & conflict {
1373
- Reason : conflictReason {
1374
- Name : responseName ,
1375
- Message : conflictReasons ,
1376
- },
1377
- FieldsLeft : conflictFieldsLeft ,
1378
- FieldsRight : conflictFieldsRight ,
1379
- }
1424
+ subfieldConflicts = func (conflicts []* conflict , responseName string , ast1 * ast.Field , ast2 * ast.Field ) * conflict {
1425
+ if len (conflicts ) > 0 {
1426
+ conflictReasons := []conflictReason {}
1427
+ conflictFieldsLeft := []ast.Node {ast1 }
1428
+ conflictFieldsRight := []ast.Node {ast2 }
1429
+ for _ , c := range conflicts {
1430
+ conflictReasons = append (conflictReasons , c .Reason )
1431
+ conflictFieldsLeft = append (conflictFieldsLeft , c .FieldsLeft ... )
1432
+ conflictFieldsRight = append (conflictFieldsRight , c .FieldsRight ... )
1433
+ }
1434
+
1435
+ return & conflict {
1436
+ Reason : conflictReason {
1437
+ Name : responseName ,
1438
+ Message : conflictReasons ,
1439
+ },
1440
+ FieldsLeft : conflictFieldsLeft ,
1441
+ FieldsRight : conflictFieldsRight ,
1380
1442
}
1381
1443
}
1382
1444
return nil
1383
1445
}
1384
-
1385
- findConflicts = func (fieldMap map [string ][]* fieldDefPair ) (conflicts []* conflict ) {
1446
+ findConflicts = func (parentFieldsAreMutuallyExclusive bool , fieldMap map [string ][]* fieldDefPair ) (conflicts []* conflict ) {
1386
1447
1387
1448
// ensure field traversal
1388
1449
orderedName := sort.StringSlice {}
@@ -1395,7 +1456,7 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul
1395
1456
fields , _ := fieldMap [responseName ]
1396
1457
for _ , fieldA := range fields {
1397
1458
for _ , fieldB := range fields {
1398
- c := findConflict (responseName , fieldA , fieldB )
1459
+ c := findConflict (parentFieldsAreMutuallyExclusive , responseName , fieldA , fieldB )
1399
1460
if c != nil {
1400
1461
conflicts = append (conflicts , c )
1401
1462
}
@@ -1429,6 +1490,9 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul
1429
1490
visitorOpts := & visitor.VisitorOptions {
1430
1491
KindFuncMap : map [string ]visitor.NamedVisitFuncs {
1431
1492
kinds .SelectionSet : visitor.NamedVisitFuncs {
1493
+ // Note: we validate on the reverse traversal so deeper conflicts will be
1494
+ // caught first, for correct calculation of mutual exclusivity and for
1495
+ // clearer error messages.
1432
1496
Leave : func (p visitor.VisitFuncParams ) (string , interface {}) {
1433
1497
if selectionSet , ok := p .Node .(* ast.SelectionSet ); ok && selectionSet != nil {
1434
1498
parentType , _ := context .ParentType ().(Named )
@@ -1439,7 +1503,7 @@ func OverlappingFieldsCanBeMergedRule(context *ValidationContext) *ValidationRul
1439
1503
nil ,
1440
1504
nil ,
1441
1505
)
1442
- conflicts := findConflicts (fieldMap )
1506
+ conflicts := findConflicts (false , fieldMap )
1443
1507
if len (conflicts ) > 0 {
1444
1508
for _ , c := range conflicts {
1445
1509
responseName := c .Reason .Name
0 commit comments