1 package validator
2
3 import (
4 "bytes"
5 "fmt"
6 "reflect"
7
8 "github.com/vektah/gqlparser/ast"
9 . "github.com/vektah/gqlparser/validator"
10 )
11
12 func init() {
13
14 AddRule("OverlappingFieldsCanBeMerged", func(observers *Events, addError AddErrFunc) {
15
69
70 m := &overlappingFieldsCanBeMergedManager{
71 comparedFragmentPairs: pairSet{data: make(map[string]map[string]bool)},
72 }
73
74 observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) {
75 m.walker = walker
76 conflicts := m.findConflictsWithinSelectionSet(operation.SelectionSet)
77 for _, conflict := range conflicts {
78 conflict.addFieldsConflictMessage(addError)
79 }
80 })
81 observers.OnField(func(walker *Walker, field *ast.Field) {
82 if walker.CurrentOperation == nil {
83
84 return
85 }
86 m.walker = walker
87 conflicts := m.findConflictsWithinSelectionSet(field.SelectionSet)
88 for _, conflict := range conflicts {
89 conflict.addFieldsConflictMessage(addError)
90 }
91 })
92 observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) {
93 m.walker = walker
94 conflicts := m.findConflictsWithinSelectionSet(inlineFragment.SelectionSet)
95 for _, conflict := range conflicts {
96 conflict.addFieldsConflictMessage(addError)
97 }
98 })
99 observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) {
100 m.walker = walker
101 conflicts := m.findConflictsWithinSelectionSet(fragment.SelectionSet)
102 for _, conflict := range conflicts {
103 conflict.addFieldsConflictMessage(addError)
104 }
105 })
106 })
107 }
108
109 type pairSet struct {
110 data map[string]map[string]bool
111 }
112
113 func (pairSet *pairSet) Add(a *ast.FragmentSpread, b *ast.FragmentSpread, areMutuallyExclusive bool) {
114 add := func(a *ast.FragmentSpread, b *ast.FragmentSpread) {
115 m := pairSet.data[a.Name]
116 if m == nil {
117 m = make(map[string]bool)
118 pairSet.data[a.Name] = m
119 }
120 m[b.Name] = areMutuallyExclusive
121 }
122 add(a, b)
123 add(b, a)
124 }
125
126 func (pairSet *pairSet) Has(a *ast.FragmentSpread, b *ast.FragmentSpread, areMutuallyExclusive bool) bool {
127 am, ok := pairSet.data[a.Name]
128 if !ok {
129 return false
130 }
131 result, ok := am[b.Name]
132 if !ok {
133 return false
134 }
135
136
137
138
139 if !areMutuallyExclusive {
140 return !result
141 }
142
143 return true
144 }
145
146 type sequentialFieldsMap struct {
147
148 seq []string
149 data map[string][]*ast.Field
150 }
151
152 type fieldIterateEntry struct {
153 ResponseName string
154 Fields []*ast.Field
155 }
156
157 func (m *sequentialFieldsMap) Push(responseName string, field *ast.Field) {
158 fields, ok := m.data[responseName]
159 if !ok {
160 m.seq = append(m.seq, responseName)
161 }
162 fields = append(fields, field)
163 m.data[responseName] = fields
164 }
165
166 func (m *sequentialFieldsMap) Get(responseName string) ([]*ast.Field, bool) {
167 fields, ok := m.data[responseName]
168 return fields, ok
169 }
170
171 func (m *sequentialFieldsMap) Iterator() [][]*ast.Field {
172 fieldsList := make([][]*ast.Field, 0, len(m.seq))
173 for _, responseName := range m.seq {
174 fields := m.data[responseName]
175 fieldsList = append(fieldsList, fields)
176 }
177 return fieldsList
178 }
179
180 func (m *sequentialFieldsMap) KeyValueIterator() []*fieldIterateEntry {
181 fieldEntriesList := make([]*fieldIterateEntry, 0, len(m.seq))
182 for _, responseName := range m.seq {
183 fields := m.data[responseName]
184 fieldEntriesList = append(fieldEntriesList, &fieldIterateEntry{
185 ResponseName: responseName,
186 Fields: fields,
187 })
188 }
189 return fieldEntriesList
190 }
191
192 type conflictMessageContainer struct {
193 Conflicts []*ConflictMessage
194 }
195
196 type ConflictMessage struct {
197 Message string
198 ResponseName string
199 Names []string
200 SubMessage []*ConflictMessage
201 Position *ast.Position
202 }
203
204 func (m *ConflictMessage) String(buf *bytes.Buffer) {
205 if len(m.SubMessage) == 0 {
206 buf.WriteString(m.Message)
207 return
208 }
209
210 for idx, subMessage := range m.SubMessage {
211 buf.WriteString(`subfields "`)
212 buf.WriteString(subMessage.ResponseName)
213 buf.WriteString(`" conflict because `)
214 subMessage.String(buf)
215 if idx != len(m.SubMessage)-1 {
216 buf.WriteString(" and ")
217 }
218 }
219 }
220
221 func (m *ConflictMessage) addFieldsConflictMessage(addError AddErrFunc) {
222 var buf bytes.Buffer
223 m.String(&buf)
224 addError(
225 Message(`Fields "%s" conflict because %s. Use different aliases on the fields to fetch both if this was intentional.`, m.ResponseName, buf.String()),
226 At(m.Position),
227 )
228 }
229
230 type overlappingFieldsCanBeMergedManager struct {
231 walker *Walker
232
233
234 comparedFragmentPairs pairSet
235
236
237
238 comparedFragments map[string]bool
239 }
240
241 func (m *overlappingFieldsCanBeMergedManager) findConflictsWithinSelectionSet(selectionSet ast.SelectionSet) []*ConflictMessage {
242 if len(selectionSet) == 0 {
243 return nil
244 }
245
246 fieldsMap, fragmentSpreads := getFieldsAndFragmentNames(selectionSet)
247
248 var conflicts conflictMessageContainer
249
250
251
252 m.collectConflictsWithin(&conflicts, fieldsMap)
253
254 m.comparedFragments = make(map[string]bool)
255 for idx, fragmentSpreadA := range fragmentSpreads {
256
257
258 m.collectConflictsBetweenFieldsAndFragment(&conflicts, false, fieldsMap, fragmentSpreadA)
259
260 for _, fragmentSpreadB := range fragmentSpreads[idx+1:] {
261
262
263
264
265 m.collectConflictsBetweenFragments(&conflicts, false, fragmentSpreadA, fragmentSpreadB)
266 }
267 }
268
269 return conflicts.Conflicts
270 }
271
272 func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFieldsAndFragment(conflicts *conflictMessageContainer, areMutuallyExclusive bool, fieldsMap *sequentialFieldsMap, fragmentSpread *ast.FragmentSpread) {
273 if m.comparedFragments[fragmentSpread.Name] {
274 return
275 }
276 m.comparedFragments[fragmentSpread.Name] = true
277
278 if fragmentSpread.Definition == nil {
279 return
280 }
281
282 fieldsMapB, fragmentSpreads := getFieldsAndFragmentNames(fragmentSpread.Definition.SelectionSet)
283
284
285 if reflect.DeepEqual(fieldsMap, fieldsMapB) {
286 return
287 }
288
289
290
291 m.collectConflictsBetween(conflicts, areMutuallyExclusive, fieldsMap, fieldsMapB)
292
293
294
295 baseFragmentSpread := fragmentSpread
296 for _, fragmentSpread := range fragmentSpreads {
297 if fragmentSpread.Name == baseFragmentSpread.Name {
298 continue
299 }
300 m.collectConflictsBetweenFieldsAndFragment(conflicts, areMutuallyExclusive, fieldsMap, fragmentSpread)
301 }
302 }
303
304 func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetweenFragments(conflicts *conflictMessageContainer, areMutuallyExclusive bool, fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread) {
305
306 var check func(fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread)
307 check = func(fragmentSpreadA *ast.FragmentSpread, fragmentSpreadB *ast.FragmentSpread) {
308
309 if fragmentSpreadA.Name == fragmentSpreadB.Name {
310 return
311 }
312
313 if m.comparedFragmentPairs.Has(fragmentSpreadA, fragmentSpreadB, areMutuallyExclusive) {
314 return
315 }
316 m.comparedFragmentPairs.Add(fragmentSpreadA, fragmentSpreadB, areMutuallyExclusive)
317
318 if fragmentSpreadA.Definition == nil {
319 return
320 }
321 if fragmentSpreadB.Definition == nil {
322 return
323 }
324
325 fieldsMapA, fragmentSpreadsA := getFieldsAndFragmentNames(fragmentSpreadA.Definition.SelectionSet)
326 fieldsMapB, fragmentSpreadsB := getFieldsAndFragmentNames(fragmentSpreadB.Definition.SelectionSet)
327
328
329
330 m.collectConflictsBetween(conflicts, areMutuallyExclusive, fieldsMapA, fieldsMapB)
331
332
333
334 for _, fragmentSpread := range fragmentSpreadsB {
335 check(fragmentSpreadA, fragmentSpread)
336 }
337
338
339 for _, fragmentSpread := range fragmentSpreadsA {
340 check(fragmentSpread, fragmentSpreadB)
341 }
342 }
343
344 check(fragmentSpreadA, fragmentSpreadB)
345 }
346
347 func (m *overlappingFieldsCanBeMergedManager) findConflictsBetweenSubSelectionSets(areMutuallyExclusive bool, selectionSetA ast.SelectionSet, selectionSetB ast.SelectionSet) *conflictMessageContainer {
348 var conflicts conflictMessageContainer
349
350 fieldsMapA, fragmentSpreadsA := getFieldsAndFragmentNames(selectionSetA)
351 fieldsMapB, fragmentSpreadsB := getFieldsAndFragmentNames(selectionSetB)
352
353
354 m.collectConflictsBetween(&conflicts, areMutuallyExclusive, fieldsMapA, fieldsMapB)
355
356
357
358 for _, fragmentSpread := range fragmentSpreadsB {
359 m.comparedFragments = make(map[string]bool)
360 m.collectConflictsBetweenFieldsAndFragment(&conflicts, areMutuallyExclusive, fieldsMapA, fragmentSpread)
361 }
362
363
364
365 for _, fragmentSpread := range fragmentSpreadsA {
366 m.comparedFragments = make(map[string]bool)
367 m.collectConflictsBetweenFieldsAndFragment(&conflicts, areMutuallyExclusive, fieldsMapB, fragmentSpread)
368 }
369
370
371
372
373 for _, fragmentSpreadA := range fragmentSpreadsA {
374 for _, fragmentSpreadB := range fragmentSpreadsB {
375 m.collectConflictsBetweenFragments(&conflicts, areMutuallyExclusive, fragmentSpreadA, fragmentSpreadB)
376 }
377 }
378
379 if len(conflicts.Conflicts) == 0 {
380 return nil
381 }
382
383 return &conflicts
384 }
385
386 func (m *overlappingFieldsCanBeMergedManager) collectConflictsWithin(conflicts *conflictMessageContainer, fieldsMap *sequentialFieldsMap) {
387 for _, fields := range fieldsMap.Iterator() {
388 for idx, fieldA := range fields {
389 for _, fieldB := range fields[idx+1:] {
390 conflict := m.findConflict(false, fieldA, fieldB)
391 if conflict != nil {
392 conflicts.Conflicts = append(conflicts.Conflicts, conflict)
393 }
394 }
395 }
396 }
397 }
398
399 func (m *overlappingFieldsCanBeMergedManager) collectConflictsBetween(conflicts *conflictMessageContainer, parentFieldsAreMutuallyExclusive bool, fieldsMapA *sequentialFieldsMap, fieldsMapB *sequentialFieldsMap) {
400 for _, fieldsEntryA := range fieldsMapA.KeyValueIterator() {
401 fieldsB, ok := fieldsMapB.Get(fieldsEntryA.ResponseName)
402 if !ok {
403 continue
404 }
405 for _, fieldA := range fieldsEntryA.Fields {
406 for _, fieldB := range fieldsB {
407 conflict := m.findConflict(parentFieldsAreMutuallyExclusive, fieldA, fieldB)
408 if conflict != nil {
409 conflicts.Conflicts = append(conflicts.Conflicts, conflict)
410 }
411 }
412 }
413 }
414 }
415
416 func (m *overlappingFieldsCanBeMergedManager) findConflict(parentFieldsAreMutuallyExclusive bool, fieldA *ast.Field, fieldB *ast.Field) *ConflictMessage {
417 if fieldA.Definition == nil || fieldA.ObjectDefinition == nil || fieldB.Definition == nil || fieldB.ObjectDefinition == nil {
418 return nil
419 }
420
421 areMutuallyExclusive := parentFieldsAreMutuallyExclusive
422 if !areMutuallyExclusive {
423 tmp := fieldA.ObjectDefinition.Name != fieldB.ObjectDefinition.Name
424 tmp = tmp && fieldA.ObjectDefinition.Kind == ast.Object
425 tmp = tmp && fieldB.ObjectDefinition.Kind == ast.Object
426 areMutuallyExclusive = tmp
427 }
428
429 fieldNameA := fieldA.Name
430 if fieldA.Alias != "" {
431 fieldNameA = fieldA.Alias
432 }
433
434 if !areMutuallyExclusive {
435
436 if fieldA.Name != fieldB.Name {
437 return &ConflictMessage{
438 ResponseName: fieldNameA,
439 Message: fmt.Sprintf(`%s and %s are different fields`, fieldA.Name, fieldB.Name),
440 Position: fieldB.Position,
441 }
442 }
443
444
445 if !sameArguments(fieldA.Arguments, fieldB.Arguments) {
446 return &ConflictMessage{
447 ResponseName: fieldNameA,
448 Message: "they have differing arguments",
449 Position: fieldB.Position,
450 }
451 }
452 }
453
454 if doTypesConflict(m.walker, fieldA.Definition.Type, fieldB.Definition.Type) {
455 return &ConflictMessage{
456 ResponseName: fieldNameA,
457 Message: fmt.Sprintf(`they return conflicting types %s and %s`, fieldA.Definition.Type.String(), fieldB.Definition.Type.String()),
458 Position: fieldB.Position,
459 }
460 }
461
462
463
464
465 conflicts := m.findConflictsBetweenSubSelectionSets(areMutuallyExclusive, fieldA.SelectionSet, fieldB.SelectionSet)
466 if conflicts == nil {
467 return nil
468 }
469 return &ConflictMessage{
470 ResponseName: fieldNameA,
471 SubMessage: conflicts.Conflicts,
472 Position: fieldB.Position,
473 }
474 }
475
476 func sameArguments(args1 []*ast.Argument, args2 []*ast.Argument) bool {
477 if len(args1) != len(args2) {
478 return false
479 }
480 for _, arg1 := range args1 {
481 for _, arg2 := range args2 {
482 if arg1.Name != arg2.Name {
483 return false
484 }
485 if !sameValue(arg1.Value, arg2.Value) {
486 return false
487 }
488 }
489 }
490 return true
491 }
492
493 func sameValue(value1 *ast.Value, value2 *ast.Value) bool {
494 if value1.Kind != value2.Kind {
495 return false
496 }
497 if value1.Raw != value2.Raw {
498 return false
499 }
500 return true
501 }
502
503 func doTypesConflict(walker *Walker, type1 *ast.Type, type2 *ast.Type) bool {
504 if type1.Elem != nil {
505 if type2.Elem != nil {
506 return doTypesConflict(walker, type1.Elem, type2.Elem)
507 }
508 return true
509 }
510 if type2.Elem != nil {
511 return true
512 }
513 if type1.NonNull && !type2.NonNull {
514 return true
515 }
516 if !type1.NonNull && type2.NonNull {
517 return true
518 }
519
520 t1 := walker.Schema.Types[type1.NamedType]
521 t2 := walker.Schema.Types[type2.NamedType]
522 if (t1.Kind == ast.Scalar || t1.Kind == ast.Enum) && (t2.Kind == ast.Scalar || t2.Kind == ast.Enum) {
523 return t1.Name != t2.Name
524 }
525
526 return false
527 }
528
529 func getFieldsAndFragmentNames(selectionSet ast.SelectionSet) (*sequentialFieldsMap, []*ast.FragmentSpread) {
530 fieldsMap := sequentialFieldsMap{
531 data: make(map[string][]*ast.Field),
532 }
533 var fragmentSpreads []*ast.FragmentSpread
534
535 var walk func(selectionSet ast.SelectionSet)
536 walk = func(selectionSet ast.SelectionSet) {
537 for _, selection := range selectionSet {
538 switch selection := selection.(type) {
539 case *ast.Field:
540 responseName := selection.Name
541 if selection.Alias != "" {
542 responseName = selection.Alias
543 }
544 fieldsMap.Push(responseName, selection)
545
546 case *ast.InlineFragment:
547 walk(selection.SelectionSet)
548
549 case *ast.FragmentSpread:
550 fragmentSpreads = append(fragmentSpreads, selection)
551 }
552 }
553 }
554 walk(selectionSet)
555
556 return &fieldsMap, fragmentSpreads
557 }
558
View as plain text