...
1 package validator
2
3 import (
4 "fmt"
5 "strings"
6
7 "github.com/vektah/gqlparser/ast"
8 . "github.com/vektah/gqlparser/validator"
9 )
10
11 func init() {
12 AddRule("NoFragmentCycles", func(observers *Events, addError AddErrFunc) {
13 visitedFrags := make(map[string]bool)
14
15 observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) {
16 var spreadPath []*ast.FragmentSpread
17 spreadPathIndexByName := make(map[string]int)
18
19 var recursive func(fragment *ast.FragmentDefinition)
20 recursive = func(fragment *ast.FragmentDefinition) {
21 if visitedFrags[fragment.Name] {
22 return
23 }
24
25 visitedFrags[fragment.Name] = true
26
27 spreadNodes := getFragmentSpreads(fragment.SelectionSet)
28 if len(spreadNodes) == 0 {
29 return
30 }
31 spreadPathIndexByName[fragment.Name] = len(spreadPath)
32
33 for _, spreadNode := range spreadNodes {
34 spreadName := spreadNode.Name
35
36 cycleIndex, ok := spreadPathIndexByName[spreadName]
37
38 spreadPath = append(spreadPath, spreadNode)
39 if !ok {
40 spreadFragment := walker.Document.Fragments.ForName(spreadName)
41 if spreadFragment != nil {
42 recursive(spreadFragment)
43 }
44 } else {
45 cyclePath := spreadPath[cycleIndex : len(spreadPath)-1]
46 var fragmentNames []string
47 for _, fs := range cyclePath {
48 fragmentNames = append(fragmentNames, fs.Name)
49 }
50 var via string
51 if len(fragmentNames) != 0 {
52 via = fmt.Sprintf(" via %s", strings.Join(fragmentNames, ", "))
53 }
54 addError(
55 Message(`Cannot spread fragment "%s" within itself%s.`, spreadName, via),
56 At(spreadNode.Position),
57 )
58 }
59
60 spreadPath = spreadPath[:len(spreadPath)-1]
61 }
62
63 delete(spreadPathIndexByName, fragment.Name)
64 }
65
66 recursive(fragment)
67 })
68 })
69 }
70
71 func getFragmentSpreads(node ast.SelectionSet) []*ast.FragmentSpread {
72 var spreads []*ast.FragmentSpread
73
74 setsToVisit := []ast.SelectionSet{node}
75
76 for len(setsToVisit) != 0 {
77 set := setsToVisit[len(setsToVisit)-1]
78 setsToVisit = setsToVisit[:len(setsToVisit)-1]
79
80 for _, selection := range set {
81 switch selection := selection.(type) {
82 case *ast.FragmentSpread:
83 spreads = append(spreads, selection)
84 case *ast.Field:
85 setsToVisit = append(setsToVisit, selection.SelectionSet)
86 case *ast.InlineFragment:
87 setsToVisit = append(setsToVisit, selection.SelectionSet)
88 }
89 }
90 }
91
92 return spreads
93 }
94
View as plain text