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