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)
}