func validateFragmentUsage(v *validationScope, defns []*gqlang.Definition) []error {
	var errs []error
	for _, defn := range defns {
		if defn.Fragment == nil {
			continue
		}
		name := defn.Fragment.Name.Value
		if !v.fragments[name].used {
			errs = append(errs, &ResponseError{
				Message: fmt.Sprintf("unused fragment %s", name),
				Locations: []Location{
					astPositionToLocation(defn.Fragment.Keyword.ToPosition(v.source)),
				},
			})
		}
	}
	return errs
}
validateFragmentUsage verifies that all fragments defined in the document are used.
See https://graphql.github.io/graphql-spec/draft/#sec-Fragments-Must-Be-Used
package graphql
import (
	"fmt"
	"strconv"
	"golang.org/x/xerrors"
	"zombiezen.com/go/graphql-server/internal/gqlang"
)
func (schema *Schema) validateRequest(source string, doc *gqlang.Document) []error {
	fragments, errs := schema.validateStructure(source, doc)
	if len(errs) > 0 {
		return errs
	}
	docScope := &validationScope{
		source:    source,
		types:     schema.types,
		fragments: fragments,
	}
	for _, defn := range doc.Definitions {
		if defn.Fragment == nil {
			continue
		}
		name := defn.Fragment.Name.Value
		if err := detectFragmentCycles(docScope, 1, map[string]struct{}{name: {}}, defn.Fragment.SelectionSet); err != nil {
			return []error{err}
		}
	}
	for _, defn := range doc.Definitions {
		if defn.Operation == nil {
			continue
		}
		errs = append(errs, schema.validateOperation(source, fragments, defn.Operation)...)
	}
	errs = append(errs, validateFragmentUsage(docScope, doc.Definitions)...)
	return errs
}
func (schema *Schema) validateStructure(source string, doc *gqlang.Document) (map[string]*fragmentValidationState, []error) {
	var errs []error
	var anonPosList []gqlang.Pos
	operationsByName := make(map[string][]gqlang.Pos)
	fragmentsByName := make(map[string][]gqlang.Pos)
	fragments := make(map[string]*fragmentValidationState)
	for _, defn := range doc.Definitions {
		switch {
		case defn.Operation != nil:
			if defn.Operation.Name == nil {
				anonPosList = append(anonPosList, defn.Operation.Start)
				continue
			}
			name := defn.Operation.Name.Value
			operationsByName[name] = append(operationsByName[name], defn.Operation.Name.Start)
		case defn.Fragment != nil:
			name := defn.Fragment.Name.Value
			fragmentsByName[name] = append(fragmentsByName[name], defn.Fragment.Name.Start)
			fragments[name] = &fragmentValidationState{
				FragmentDefinition: defn.Fragment,
			}
		default:
			errs = append(errs, &ResponseError{
				Message: "not an operation nor a fragment",
				Locations: []Location{
					astPositionToLocation(defn.Start().ToPosition(source)),
				},
			})
			continue
		}
	}
	if len(anonPosList) > 1 {
		errs = append(errs, &ResponseError{
			Message:   "multiple anonymous operations",
			Locations: posListToLocationList(source, anonPosList),
		})
	}
	if len(anonPosList) > 0 && len(operationsByName) > 0 {
		errs = append(errs, &ResponseError{
			Message:   "anonymous operations mixed with named operations",
			Locations: posListToLocationList(source, anonPosList),
		})
	}
	for _, defn := range doc.Definitions {
		if defn.Operation == nil || defn.Operation.Name == nil {
			continue
		}
		name := defn.Operation.Name.Value
		posList := operationsByName[name]
		if defn.Operation.Name.Start != posList[0] {
			continue
		}
		if len(posList) > 1 {
			errs = append(errs, &ResponseError{
				Message:   fmt.Sprintf("multiple operations with name %q", name),
				Locations: posListToLocationList(source, posList),
			})
		}
	}
	for _, defn := range doc.Definitions {
		if defn.Fragment == nil {
			continue
		}
		name := defn.Fragment.Name.Value
		posList := fragmentsByName[name]
		if defn.Fragment.Name.Start != posList[0] {
			continue
		}
		if len(posList) > 1 {
			errs = append(errs, &ResponseError{
				Message:   fmt.Sprintf("multiple fragments with name %q", name),
				Locations: posListToLocationList(source, posList),
			})
		}
	}
	return fragments, errs
}
func detectFragmentCycles(v *validationScope, depth int, visited map[string]struct{}, set *gqlang.SelectionSet) error {
	if set == nil {
		return nil
	}
	if depth > 20 {
		return xerrors.New("fragment references too deep")
	}
	for _, sel := range set.Sel {
		switch {
		case sel.Field != nil:
			if err := detectFragmentCycles(v, depth+1, visited, sel.Field.SelectionSet); err != nil {
				return err
			}
		case sel.InlineFragment != nil:
			if err := detectFragmentCycles(v, depth+1, visited, sel.InlineFragment.SelectionSet); err != nil {
				return err
			}
		case sel.FragmentSpread != nil:
			name := sel.FragmentSpread.Name.Value
			if _, seen := visited[name]; seen {
				return &ResponseError{
					Message: fmt.Sprintf("fragment %s is self-referential", name),
					Locations: []Location{
						astPositionToLocation(sel.FragmentSpread.Name.Start.ToPosition(v.source)),
					},
				}
			}
			frag := v.fragments[name]
			if frag == nil {
				continue
			}
			visited[name] = struct{}{}
			if err := detectFragmentCycles(v, depth+1, visited, frag.SelectionSet); err != nil {
				return err
			}
			delete(visited, name)
		}
	}
	return nil
}
func (schema *Schema) validateOperation(source string, fragments map[string]*fragmentValidationState, op *gqlang.Operation) []error {
	opType := schema.operationType(op.Type)
	if opType == nil {
		return []error{&ResponseError{
			Message: fmt.Sprintf("%v unsupported", op.Type),
			Locations: []Location{
				astPositionToLocation(op.Start.ToPosition(source)),
			},
		}}
	}
	variables, varErrs := validateVariables(source, schema.types, op.VariableDefinitions)
	if len(varErrs) > 0 {
		if op.Name != nil {
			for i, err := range varErrs {
				varErrs[i] = xerrors.Errorf("operation %s: %w", op.Name, err)
			}
		}
		return varErrs
	}
	v := &validationScope{
		source:    source,
		types:     schema.types,
		variables: variables,
		fragments: fragments,
	}
	var errs []error
	selErrs := validateSelectionSet(v, op.Type == gqlang.Query, opType, op.SelectionSet)
	if op.Name != nil {
		for _, err := range selErrs {
			errs = append(errs, xerrors.Errorf("operation %s: %w", op.Name, err))
		}
	} else {
		errs = append(errs, selErrs...)
	}
	useErrs := validateVariableUsage(v, op.VariableDefinitions)
	if op.Name != nil {
		for _, err := range useErrs {
			errs = append(errs, xerrors.Errorf("operation %s: %w", op.Name, err))
		}
	} else {
		errs = append(errs, useErrs...)
	}
	return errs
}
type validationScope struct {
	source    string
	types     map[string]*gqlType
	variables map[string]*validatedVariable
	fragments map[string]*fragmentValidationState
}
type validatedVariable struct {
	name         string
	defaultValue Value
	used         bool
}
func (vv *validatedVariable) typ() *gqlType {
	return vv.defaultValue.typ
}
type fragmentValidationState struct {
	*gqlang.FragmentDefinition
	used bool
}
func validateVariables(source string, typeMap map[string]*gqlType, defns *gqlang.VariableDefinitions) (map[string]*validatedVariable, []error) {
	if defns == nil {
		return nil, nil
	}
	variablesByName := make(map[string][]gqlang.Pos)
	result := make(map[string]*validatedVariable)
	var errs []error
	for _, defn := range defns.Defs {
		name := defn.Var.Name.Value
		variablesByName[name] = append(variablesByName[name], defn.Var.Dollar)
		typ := resolveTypeRef(typeMap, defn.Type)
		if typ == nil {
			errs = append(errs, &ResponseError{
				Message: fmt.Sprintf("undefined type %v", defn.Type),
				Locations: []Location{
					astPositionToLocation(defn.Type.Start().ToPosition(source)),
				},
			})
			continue
		}
		if !typ.isInputType() {
			errs = append(errs, &ResponseError{
				Message: fmt.Sprintf("%v is not an input type", defn.Type),
				Locations: []Location{
					astPositionToLocation(defn.Type.Start().ToPosition(source)),
				},
			})
			continue
		}
		vv := &validatedVariable{
			name:         name,
			defaultValue: Value{typ: typ},
		}
		result[name] = vv
		if defn.Default != nil {
			defaultErrs := validateConstantValue(source, typ, defn.Default.Value)
			if len(defaultErrs) > 0 {
				for _, err := range defaultErrs {
					errs = append(errs, xerrors.Errorf("variable $%s: %w", name, err))
				}
				continue
			}
			vv.defaultValue = coerceConstantInputValue(typ, defn.Default.Value)
		}
	}
	for _, defn := range defns.Defs {
		name := defn.Var.Name.Value
		posList := variablesByName[name]
		if defn.Var.Dollar != posList[0] {
			continue
		}
		if len(posList) > 1 {
			errs = append(errs, &ResponseError{
				Message:   fmt.Sprintf("multiple variables with name %q", name),
				Locations: posListToLocationList(source, posList),
			})
		}
	}
	if len(errs) > 0 {
		return nil, errs
	}
	return result, nil
}
func validateVariableUsage(v *validationScope, defns *gqlang.VariableDefinitions) []error {
	if defns == nil {
		return nil
	}
	var errs []error
	for _, defn := range defns.Defs {
		name := defn.Var.Name.Value
		if !v.variables[name].used {
			errs = append(errs, &ResponseError{
				Message: fmt.Sprintf("unused variable $%s", name),
				Locations: []Location{
					astPositionToLocation(defn.Var.Dollar.ToPosition(v.source)),
				},
			})
		}
	}
	return errs
}