aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/vektah/gqlparser/validator/rules/fields_on_correct_type.go
blob: 69148d52647f06dae12b6cb54468e90f3448a534 (plain) (blame)
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
package validator

import (
	"fmt"
	"sort"

	"github.com/vektah/gqlparser/ast"
	. "github.com/vektah/gqlparser/validator"
)

func init() {
	AddRule("FieldsOnCorrectType", func(observers *Events, addError AddErrFunc) {
		observers.OnField(func(walker *Walker, field *ast.Field) {
			if field.ObjectDefinition == nil || field.Definition != nil {
				return
			}

			message := fmt.Sprintf(`Cannot query field "%s" on type "%s".`, field.Name, field.ObjectDefinition.Name)

			if suggestedTypeNames := getSuggestedTypeNames(walker, field.ObjectDefinition, field.Name); suggestedTypeNames != nil {
				message += " Did you mean to use an inline fragment on " + QuotedOrList(suggestedTypeNames...) + "?"
			} else if suggestedFieldNames := getSuggestedFieldNames(field.ObjectDefinition, field.Name); suggestedFieldNames != nil {
				message += " Did you mean " + QuotedOrList(suggestedFieldNames...) + "?"
			}

			addError(
				Message(message),
				At(field.Position),
			)
		})
	})
}

// Go through all of the implementations of type, as well as the interfaces
// that they implement. If any of those types include the provided field,
// suggest them, sorted by how often the type is referenced,  starting
// with Interfaces.
func getSuggestedTypeNames(walker *Walker, parent *ast.Definition, name string) []string {
	if !parent.IsAbstractType() {
		return nil
	}

	var suggestedObjectTypes []string
	var suggestedInterfaceTypes []string
	interfaceUsageCount := map[string]int{}

	for _, possibleType := range walker.Schema.GetPossibleTypes(parent) {
		field := possibleType.Fields.ForName(name)
		if field == nil {
			continue
		}

		suggestedObjectTypes = append(suggestedObjectTypes, possibleType.Name)

		for _, possibleInterface := range possibleType.Interfaces {
			interfaceField := walker.Schema.Types[possibleInterface]
			if interfaceField != nil && interfaceField.Fields.ForName(name) != nil {
				if interfaceUsageCount[possibleInterface] == 0 {
					suggestedInterfaceTypes = append(suggestedInterfaceTypes, possibleInterface)
				}
				interfaceUsageCount[possibleInterface]++
			}
		}
	}

	sort.SliceStable(suggestedInterfaceTypes, func(i, j int) bool {
		return interfaceUsageCount[suggestedInterfaceTypes[i]] > interfaceUsageCount[suggestedInterfaceTypes[j]]
	})

	return append(suggestedInterfaceTypes, suggestedObjectTypes...)
}

// For the field name provided, determine if there are any similar field names
// that may be the result of a typo.
func getSuggestedFieldNames(parent *ast.Definition, name string) []string {
	if parent.Kind != ast.Object && parent.Kind != ast.Interface {
		return nil
	}

	var possibleFieldNames []string
	for _, field := range parent.Fields {
		possibleFieldNames = append(possibleFieldNames, field.Name)
	}

	return SuggestionList(name, possibleFieldNames)
}