...

Source file src/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go

Documentation: github.com/vektah/gqlparser/v2/validator/rules

     1  package validator
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/vektah/gqlparser/v2/ast"
     8  
     9  	//nolint:revive // Validator rules each use dot imports for convenience.
    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