File: single_field_subscriptions.go

package info (click to toggle)
golang-github-vektah-gqlparser 2.5.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,748 kB
  • sloc: javascript: 164; sh: 46; makefile: 10
file content (88 lines) | stat: -rw-r--r-- 2,188 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package validator

import (
	"strconv"
	"strings"

	"github.com/vektah/gqlparser/v2/ast"

	//nolint:revive // Validator rules each use dot imports for convenience.
	. "github.com/vektah/gqlparser/v2/validator"
)

func init() {
	AddRule("SingleFieldSubscriptions", func(observers *Events, addError AddErrFunc) {
		observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) {
			if walker.Schema.Subscription == nil || operation.Operation != ast.Subscription {
				return
			}

			fields := retrieveTopFieldNames(operation.SelectionSet)

			name := "Anonymous Subscription"
			if operation.Name != "" {
				name = `Subscription ` + strconv.Quote(operation.Name)
			}

			if len(fields) > 1 {
				addError(
					Message(`%s must select only one top level field.`, name),
					At(fields[1].position),
				)
			}

			for _, field := range fields {
				if strings.HasPrefix(field.name, "__") {
					addError(
						Message(`%s must not select an introspection top level field.`, name),
						At(field.position),
					)
				}
			}
		})
	})
}

type topField struct {
	name     string
	position *ast.Position
}

func retrieveTopFieldNames(selectionSet ast.SelectionSet) []*topField {
	fields := []*topField{}
	inFragmentRecursive := map[string]bool{}
	var walk func(selectionSet ast.SelectionSet)
	walk = func(selectionSet ast.SelectionSet) {
		for _, selection := range selectionSet {
			switch selection := selection.(type) {
			case *ast.Field:
				fields = append(fields, &topField{
					name:     selection.Name,
					position: selection.GetPosition(),
				})
			case *ast.InlineFragment:
				walk(selection.SelectionSet)
			case *ast.FragmentSpread:
				if selection.Definition == nil {
					return
				}
				fragment := selection.Definition.Name
				if !inFragmentRecursive[fragment] {
					inFragmentRecursive[fragment] = true
					walk(selection.Definition.SelectionSet)
				}
			}
		}
	}
	walk(selectionSet)

	seen := make(map[string]bool, len(fields))
	uniquedFields := make([]*topField, 0, len(fields))
	for _, field := range fields {
		if !seen[field.name] {
			uniquedFields = append(uniquedFields, field)
		}
		seen[field.name] = true
	}
	return uniquedFields
}