aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/graphql-go/graphql/validator.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/graphql-go/graphql/validator.go')
-rw-r--r--vendor/github.com/graphql-go/graphql/validator.go288
1 files changed, 288 insertions, 0 deletions
diff --git a/vendor/github.com/graphql-go/graphql/validator.go b/vendor/github.com/graphql-go/graphql/validator.go
new file mode 100644
index 00000000..73c213eb
--- /dev/null
+++ b/vendor/github.com/graphql-go/graphql/validator.go
@@ -0,0 +1,288 @@
+package graphql
+
+import (
+ "github.com/graphql-go/graphql/gqlerrors"
+ "github.com/graphql-go/graphql/language/ast"
+ "github.com/graphql-go/graphql/language/kinds"
+ "github.com/graphql-go/graphql/language/visitor"
+)
+
+type ValidationResult struct {
+ IsValid bool
+ Errors []gqlerrors.FormattedError
+}
+
+/**
+ * Implements the "Validation" section of the spec.
+ *
+ * Validation runs synchronously, returning an array of encountered errors, or
+ * an empty array if no errors were encountered and the document is valid.
+ *
+ * A list of specific validation rules may be provided. If not provided, the
+ * default list of rules defined by the GraphQL specification will be used.
+ *
+ * Each validation rules is a function which returns a visitor
+ * (see the language/visitor API). Visitor methods are expected to return
+ * GraphQLErrors, or Arrays of GraphQLErrors when invalid.
+ */
+
+func ValidateDocument(schema *Schema, astDoc *ast.Document, rules []ValidationRuleFn) (vr ValidationResult) {
+ if len(rules) == 0 {
+ rules = SpecifiedRules
+ }
+
+ vr.IsValid = false
+ if schema == nil {
+ vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide schema"))
+ return vr
+ }
+ if astDoc == nil {
+ vr.Errors = append(vr.Errors, gqlerrors.NewFormattedError("Must provide document"))
+ return vr
+ }
+
+ typeInfo := NewTypeInfo(&TypeInfoConfig{
+ Schema: schema,
+ })
+ vr.Errors = VisitUsingRules(schema, typeInfo, astDoc, rules)
+ if len(vr.Errors) == 0 {
+ vr.IsValid = true
+ }
+ return vr
+}
+
+// VisitUsingRules This uses a specialized visitor which runs multiple visitors in parallel,
+// while maintaining the visitor skip and break API.
+//
+// @internal
+// Had to expose it to unit test experimental customizable validation feature,
+// but not meant for public consumption
+func VisitUsingRules(schema *Schema, typeInfo *TypeInfo, astDoc *ast.Document, rules []ValidationRuleFn) []gqlerrors.FormattedError {
+
+ context := NewValidationContext(schema, astDoc, typeInfo)
+ visitors := []*visitor.VisitorOptions{}
+
+ for _, rule := range rules {
+ instance := rule(context)
+ visitors = append(visitors, instance.VisitorOpts)
+ }
+
+ // Visit the whole document with each instance of all provided rules.
+ visitor.Visit(astDoc, visitor.VisitWithTypeInfo(typeInfo, visitor.VisitInParallel(visitors...)), nil)
+ return context.Errors()
+}
+
+type HasSelectionSet interface {
+ GetKind() string
+ GetLoc() *ast.Location
+ GetSelectionSet() *ast.SelectionSet
+}
+
+var _ HasSelectionSet = (*ast.OperationDefinition)(nil)
+var _ HasSelectionSet = (*ast.FragmentDefinition)(nil)
+
+type VariableUsage struct {
+ Node *ast.Variable
+ Type Input
+}
+
+type ValidationContext struct {
+ schema *Schema
+ astDoc *ast.Document
+ typeInfo *TypeInfo
+ errors []gqlerrors.FormattedError
+ fragments map[string]*ast.FragmentDefinition
+ variableUsages map[HasSelectionSet][]*VariableUsage
+ recursiveVariableUsages map[*ast.OperationDefinition][]*VariableUsage
+ recursivelyReferencedFragments map[*ast.OperationDefinition][]*ast.FragmentDefinition
+ fragmentSpreads map[*ast.SelectionSet][]*ast.FragmentSpread
+}
+
+func NewValidationContext(schema *Schema, astDoc *ast.Document, typeInfo *TypeInfo) *ValidationContext {
+ return &ValidationContext{
+ schema: schema,
+ astDoc: astDoc,
+ typeInfo: typeInfo,
+ fragments: map[string]*ast.FragmentDefinition{},
+ variableUsages: map[HasSelectionSet][]*VariableUsage{},
+ recursiveVariableUsages: map[*ast.OperationDefinition][]*VariableUsage{},
+ recursivelyReferencedFragments: map[*ast.OperationDefinition][]*ast.FragmentDefinition{},
+ fragmentSpreads: map[*ast.SelectionSet][]*ast.FragmentSpread{},
+ }
+}
+
+func (ctx *ValidationContext) ReportError(err error) {
+ formattedErr := gqlerrors.FormatError(err)
+ ctx.errors = append(ctx.errors, formattedErr)
+}
+func (ctx *ValidationContext) Errors() []gqlerrors.FormattedError {
+ return ctx.errors
+}
+
+func (ctx *ValidationContext) Schema() *Schema {
+ return ctx.schema
+}
+func (ctx *ValidationContext) Document() *ast.Document {
+ return ctx.astDoc
+}
+func (ctx *ValidationContext) Fragment(name string) *ast.FragmentDefinition {
+ if len(ctx.fragments) == 0 {
+ if ctx.Document() == nil {
+ return nil
+ }
+ defs := ctx.Document().Definitions
+ fragments := map[string]*ast.FragmentDefinition{}
+ for _, def := range defs {
+ if def, ok := def.(*ast.FragmentDefinition); ok {
+ defName := ""
+ if def.Name != nil {
+ defName = def.Name.Value
+ }
+ fragments[defName] = def
+ }
+ }
+ ctx.fragments = fragments
+ }
+ f, _ := ctx.fragments[name]
+ return f
+}
+func (ctx *ValidationContext) FragmentSpreads(node *ast.SelectionSet) []*ast.FragmentSpread {
+ if spreads, ok := ctx.fragmentSpreads[node]; ok && spreads != nil {
+ return spreads
+ }
+
+ spreads := []*ast.FragmentSpread{}
+ setsToVisit := []*ast.SelectionSet{node}
+
+ for {
+ if len(setsToVisit) == 0 {
+ break
+ }
+ var set *ast.SelectionSet
+ // pop
+ set, setsToVisit = setsToVisit[len(setsToVisit)-1], setsToVisit[:len(setsToVisit)-1]
+ if set.Selections != nil {
+ for _, selection := range set.Selections {
+ switch selection := selection.(type) {
+ case *ast.FragmentSpread:
+ spreads = append(spreads, selection)
+ case *ast.Field:
+ if selection.SelectionSet != nil {
+ setsToVisit = append(setsToVisit, selection.SelectionSet)
+ }
+ case *ast.InlineFragment:
+ if selection.SelectionSet != nil {
+ setsToVisit = append(setsToVisit, selection.SelectionSet)
+ }
+ }
+ }
+ }
+ ctx.fragmentSpreads[node] = spreads
+ }
+ return spreads
+}
+
+func (ctx *ValidationContext) RecursivelyReferencedFragments(operation *ast.OperationDefinition) []*ast.FragmentDefinition {
+ if fragments, ok := ctx.recursivelyReferencedFragments[operation]; ok && fragments != nil {
+ return fragments
+ }
+
+ fragments := []*ast.FragmentDefinition{}
+ collectedNames := map[string]bool{}
+ nodesToVisit := []*ast.SelectionSet{operation.SelectionSet}
+
+ for {
+ if len(nodesToVisit) == 0 {
+ break
+ }
+
+ var node *ast.SelectionSet
+
+ node, nodesToVisit = nodesToVisit[len(nodesToVisit)-1], nodesToVisit[:len(nodesToVisit)-1]
+ spreads := ctx.FragmentSpreads(node)
+ for _, spread := range spreads {
+ fragName := ""
+ if spread.Name != nil {
+ fragName = spread.Name.Value
+ }
+ if res, ok := collectedNames[fragName]; !ok || !res {
+ collectedNames[fragName] = true
+ fragment := ctx.Fragment(fragName)
+ if fragment != nil {
+ fragments = append(fragments, fragment)
+ nodesToVisit = append(nodesToVisit, fragment.SelectionSet)
+ }
+ }
+
+ }
+ }
+
+ ctx.recursivelyReferencedFragments[operation] = fragments
+ return fragments
+}
+func (ctx *ValidationContext) VariableUsages(node HasSelectionSet) []*VariableUsage {
+ if usages, ok := ctx.variableUsages[node]; ok && usages != nil {
+ return usages
+ }
+ usages := []*VariableUsage{}
+ typeInfo := NewTypeInfo(&TypeInfoConfig{
+ Schema: ctx.schema,
+ })
+
+ visitor.Visit(node, visitor.VisitWithTypeInfo(typeInfo, &visitor.VisitorOptions{
+ KindFuncMap: map[string]visitor.NamedVisitFuncs{
+ kinds.VariableDefinition: {
+ Kind: func(p visitor.VisitFuncParams) (string, interface{}) {
+ return visitor.ActionSkip, nil
+ },
+ },
+ kinds.Variable: {
+ Kind: func(p visitor.VisitFuncParams) (string, interface{}) {
+ if node, ok := p.Node.(*ast.Variable); ok && node != nil {
+ usages = append(usages, &VariableUsage{
+ Node: node,
+ Type: typeInfo.InputType(),
+ })
+ }
+ return visitor.ActionNoChange, nil
+ },
+ },
+ },
+ }), nil)
+
+ ctx.variableUsages[node] = usages
+ return usages
+}
+func (ctx *ValidationContext) RecursiveVariableUsages(operation *ast.OperationDefinition) []*VariableUsage {
+ if usages, ok := ctx.recursiveVariableUsages[operation]; ok && usages != nil {
+ return usages
+ }
+ usages := ctx.VariableUsages(operation)
+
+ fragments := ctx.RecursivelyReferencedFragments(operation)
+ for _, fragment := range fragments {
+ fragmentUsages := ctx.VariableUsages(fragment)
+ usages = append(usages, fragmentUsages...)
+ }
+
+ ctx.recursiveVariableUsages[operation] = usages
+ return usages
+}
+func (ctx *ValidationContext) Type() Output {
+ return ctx.typeInfo.Type()
+}
+func (ctx *ValidationContext) ParentType() Composite {
+ return ctx.typeInfo.ParentType()
+}
+func (ctx *ValidationContext) InputType() Input {
+ return ctx.typeInfo.InputType()
+}
+func (ctx *ValidationContext) FieldDef() *FieldDefinition {
+ return ctx.typeInfo.FieldDef()
+}
+func (ctx *ValidationContext) Directive() *Directive {
+ return ctx.typeInfo.Directive()
+}
+func (ctx *ValidationContext) Argument() *Argument {
+ return ctx.typeInfo.Argument()
+}