validator.go

  1package graphql
  2
  3import (
  4	"github.com/graphql-go/graphql/gqlerrors"
  5	"github.com/graphql-go/graphql/language/ast"
  6	"github.com/graphql-go/graphql/language/kinds"
  7	"github.com/graphql-go/graphql/language/visitor"
  8)
  9
 10type ValidationResult struct {
 11	IsValid bool
 12	Errors  []gqlerrors.FormattedError
 13}
 14
 15/**
 16 * Implements the "Validation" section of the spec.
 17 *
 18 * Validation runs synchronously, returning an array of encountered errors, or
 19 * an empty array if no errors were encountered and the document is valid.
 20 *
 21 * A list of specific validation rules may be provided. If not provided, the
 22 * default list of rules defined by the GraphQL specification will be used.
 23 *
 24 * Each validation rules is a function which returns a visitor
 25 * (see the language/visitor API). Visitor methods are expected to return
 26 * GraphQLErrors, or Arrays of GraphQLErrors when invalid.
 27 */
 28
 29func ValidateDocument(schema *Schema, astDoc *ast.Document, rules []ValidationRuleFn) (vr ValidationResult) {
 30	if len(rules) == 0 {
 31		rules = SpecifiedRules
 32	}
 33
 34	vr.IsValid = false
 35	if schema == nil {
 36		vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide schema"))
 37		return vr
 38	}
 39	if astDoc == nil {
 40		vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide document"))
 41		return vr
 42	}
 43
 44	typeInfo := NewTypeInfo(&TypeInfoConfig{
 45		Schema: schema,
 46	})
 47	vr.Errors = VisitUsingRules(schema, typeInfo, astDoc, rules)
 48	if len(vr.Errors) == 0 {
 49		vr.IsValid = true
 50	}
 51	return vr
 52}
 53
 54// VisitUsingRules This uses a specialized visitor which runs multiple visitors in parallel,
 55// while maintaining the visitor skip and break API.
 56//
 57// @internal
 58// Had to expose it to unit test experimental customizable validation feature,
 59// but not meant for public consumption
 60func VisitUsingRules(schema *Schema, typeInfo *TypeInfo, astDoc *ast.Document, rules []ValidationRuleFn) []gqlerrors.FormattedError {
 61
 62	context := NewValidationContext(schema, astDoc, typeInfo)
 63	visitors := []*visitor.VisitorOptions{}
 64
 65	for _, rule := range rules {
 66		instance := rule(context)
 67		visitors = append(visitors, instance.VisitorOpts)
 68	}
 69
 70	// Visit the whole document with each instance of all provided rules.
 71	visitor.Visit(astDoc, visitor.VisitWithTypeInfo(typeInfo, visitor.VisitInParallel(visitors...)), nil)
 72	return context.Errors()
 73}
 74
 75type HasSelectionSet interface {
 76	GetKind() string
 77	GetLoc() *ast.Location
 78	GetSelectionSet() *ast.SelectionSet
 79}
 80
 81var _ HasSelectionSet = (*ast.OperationDefinition)(nil)
 82var _ HasSelectionSet = (*ast.FragmentDefinition)(nil)
 83
 84type VariableUsage struct {
 85	Node *ast.Variable
 86	Type Input
 87}
 88
 89type ValidationContext struct {
 90	schema                         *Schema
 91	astDoc                         *ast.Document
 92	typeInfo                       *TypeInfo
 93	errors                         []gqlerrors.FormattedError
 94	fragments                      map[string]*ast.FragmentDefinition
 95	variableUsages                 map[HasSelectionSet][]*VariableUsage
 96	recursiveVariableUsages        map[*ast.OperationDefinition][]*VariableUsage
 97	recursivelyReferencedFragments map[*ast.OperationDefinition][]*ast.FragmentDefinition
 98	fragmentSpreads                map[*ast.SelectionSet][]*ast.FragmentSpread
 99}
100
101func NewValidationContext(schema *Schema, astDoc *ast.Document, typeInfo *TypeInfo) *ValidationContext {
102	return &ValidationContext{
103		schema:                         schema,
104		astDoc:                         astDoc,
105		typeInfo:                       typeInfo,
106		fragments:                      map[string]*ast.FragmentDefinition{},
107		variableUsages:                 map[HasSelectionSet][]*VariableUsage{},
108		recursiveVariableUsages:        map[*ast.OperationDefinition][]*VariableUsage{},
109		recursivelyReferencedFragments: map[*ast.OperationDefinition][]*ast.FragmentDefinition{},
110		fragmentSpreads:                map[*ast.SelectionSet][]*ast.FragmentSpread{},
111	}
112}
113
114func (ctx *ValidationContext) ReportError(err error) {
115	formattedErr := gqlerrors.FormatError(err)
116	ctx.errors = append(ctx.errors, formattedErr)
117}
118func (ctx *ValidationContext) Errors() []gqlerrors.FormattedError {
119	return ctx.errors
120}
121
122func (ctx *ValidationContext) Schema() *Schema {
123	return ctx.schema
124}
125func (ctx *ValidationContext) Document() *ast.Document {
126	return ctx.astDoc
127}
128func (ctx *ValidationContext) Fragment(name string) *ast.FragmentDefinition {
129	if len(ctx.fragments) == 0 {
130		if ctx.Document() == nil {
131			return nil
132		}
133		defs := ctx.Document().Definitions
134		fragments := map[string]*ast.FragmentDefinition{}
135		for _, def := range defs {
136			if def, ok := def.(*ast.FragmentDefinition); ok {
137				defName := ""
138				if def.Name != nil {
139					defName = def.Name.Value
140				}
141				fragments[defName] = def
142			}
143		}
144		ctx.fragments = fragments
145	}
146	f, _ := ctx.fragments[name]
147	return f
148}
149func (ctx *ValidationContext) FragmentSpreads(node *ast.SelectionSet) []*ast.FragmentSpread {
150	if spreads, ok := ctx.fragmentSpreads[node]; ok && spreads != nil {
151		return spreads
152	}
153
154	spreads := []*ast.FragmentSpread{}
155	setsToVisit := []*ast.SelectionSet{node}
156
157	for {
158		if len(setsToVisit) == 0 {
159			break
160		}
161		var set *ast.SelectionSet
162		// pop
163		set, setsToVisit = setsToVisit[len(setsToVisit)-1], setsToVisit[:len(setsToVisit)-1]
164		if set.Selections != nil {
165			for _, selection := range set.Selections {
166				switch selection := selection.(type) {
167				case *ast.FragmentSpread:
168					spreads = append(spreads, selection)
169				case *ast.Field:
170					if selection.SelectionSet != nil {
171						setsToVisit = append(setsToVisit, selection.SelectionSet)
172					}
173				case *ast.InlineFragment:
174					if selection.SelectionSet != nil {
175						setsToVisit = append(setsToVisit, selection.SelectionSet)
176					}
177				}
178			}
179		}
180		ctx.fragmentSpreads[node] = spreads
181	}
182	return spreads
183}
184
185func (ctx *ValidationContext) RecursivelyReferencedFragments(operation *ast.OperationDefinition) []*ast.FragmentDefinition {
186	if fragments, ok := ctx.recursivelyReferencedFragments[operation]; ok && fragments != nil {
187		return fragments
188	}
189
190	fragments := []*ast.FragmentDefinition{}
191	collectedNames := map[string]bool{}
192	nodesToVisit := []*ast.SelectionSet{operation.SelectionSet}
193
194	for {
195		if len(nodesToVisit) == 0 {
196			break
197		}
198
199		var node *ast.SelectionSet
200
201		node, nodesToVisit = nodesToVisit[len(nodesToVisit)-1], nodesToVisit[:len(nodesToVisit)-1]
202		spreads := ctx.FragmentSpreads(node)
203		for _, spread := range spreads {
204			fragName := ""
205			if spread.Name != nil {
206				fragName = spread.Name.Value
207			}
208			if res, ok := collectedNames[fragName]; !ok || !res {
209				collectedNames[fragName] = true
210				fragment := ctx.Fragment(fragName)
211				if fragment != nil {
212					fragments = append(fragments, fragment)
213					nodesToVisit = append(nodesToVisit, fragment.SelectionSet)
214				}
215			}
216
217		}
218	}
219
220	ctx.recursivelyReferencedFragments[operation] = fragments
221	return fragments
222}
223func (ctx *ValidationContext) VariableUsages(node HasSelectionSet) []*VariableUsage {
224	if usages, ok := ctx.variableUsages[node]; ok && usages != nil {
225		return usages
226	}
227	usages := []*VariableUsage{}
228	typeInfo := NewTypeInfo(&TypeInfoConfig{
229		Schema: ctx.schema,
230	})
231
232	visitor.Visit(node, visitor.VisitWithTypeInfo(typeInfo, &visitor.VisitorOptions{
233		KindFuncMap: map[string]visitor.NamedVisitFuncs{
234			kinds.VariableDefinition: {
235				Kind: func(p visitor.VisitFuncParams) (string, interface{}) {
236					return visitor.ActionSkip, nil
237				},
238			},
239			kinds.Variable: {
240				Kind: func(p visitor.VisitFuncParams) (string, interface{}) {
241					if node, ok := p.Node.(*ast.Variable); ok && node != nil {
242						usages = append(usages, &VariableUsage{
243							Node: node,
244							Type: typeInfo.InputType(),
245						})
246					}
247					return visitor.ActionNoChange, nil
248				},
249			},
250		},
251	}), nil)
252
253	ctx.variableUsages[node] = usages
254	return usages
255}
256func (ctx *ValidationContext) RecursiveVariableUsages(operation *ast.OperationDefinition) []*VariableUsage {
257	if usages, ok := ctx.recursiveVariableUsages[operation]; ok && usages != nil {
258		return usages
259	}
260	usages := ctx.VariableUsages(operation)
261
262	fragments := ctx.RecursivelyReferencedFragments(operation)
263	for _, fragment := range fragments {
264		fragmentUsages := ctx.VariableUsages(fragment)
265		usages = append(usages, fragmentUsages...)
266	}
267
268	ctx.recursiveVariableUsages[operation] = usages
269	return usages
270}
271func (ctx *ValidationContext) Type() Output {
272	return ctx.typeInfo.Type()
273}
274func (ctx *ValidationContext) ParentType() Composite {
275	return ctx.typeInfo.ParentType()
276}
277func (ctx *ValidationContext) InputType() Input {
278	return ctx.typeInfo.InputType()
279}
280func (ctx *ValidationContext) FieldDef() *FieldDefinition {
281	return ctx.typeInfo.FieldDef()
282}
283func (ctx *ValidationContext) Directive() *Directive {
284	return ctx.typeInfo.Directive()
285}
286func (ctx *ValidationContext) Argument() *Argument {
287	return ctx.typeInfo.Argument()
288}