Skip to content

Commit 96fc0ad

Browse files
committed
Minor refactoring of error messages for unknown fields
Commit: 30a783a2a4f100cad0f31085284895bf51aa565a [30a783a] Parents: 5bc1b2541d Author: Lee Byron <[email protected]> Date: 27 April 2016 at 8:49:33 AM SGT
1 parent 599c577 commit 96fc0ad

File tree

4 files changed

+147
-123
lines changed

4 files changed

+147
-123
lines changed

definition.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ var _ Output = (*NonNull)(nil)
120120
// Composite interface for types that may describe the parent context of a selection set.
121121
type Composite interface {
122122
Name() string
123+
Description() string
124+
String() string
125+
Error() error
123126
}
124127

125128
var _ Composite = (*Object)(nil)

rules.go

Lines changed: 103 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package graphql
22

33
import (
44
"fmt"
5+
"math"
6+
"sort"
7+
"strings"
8+
59
"github.com/graphql-go/graphql/gqlerrors"
610
"github.com/graphql-go/graphql/language/ast"
711
"github.com/graphql-go/graphql/language/kinds"
812
"github.com/graphql-go/graphql/language/printer"
913
"github.com/graphql-go/graphql/language/visitor"
10-
"math"
11-
"sort"
12-
"strings"
1314
)
1415

1516
// SpecifiedRules set includes all validation rules defined by the GraphQL spec.
@@ -170,32 +171,33 @@ func quoteStrings(slice []string) []string {
170171
}
171172
return quoted
172173
}
173-
func UndefinedFieldMessage(fieldName string, ttypeName string, suggestedTypes []string, suggestedFields []string) string {
174-
174+
func quotedOrList(slice []string) string {
175+
quoted := quoteStrings(slice)
176+
if len(quoted) > 1 {
177+
return fmt.Sprintf("%v or %v", strings.Join(quoted[0:len(quoted)-1], ", "), quoted[len(quoted)-1])
178+
}
179+
return quoted[0]
180+
}
181+
func UndefinedFieldMessage(fieldName string, ttypeName string, suggestedTypeNames []string, suggestedFieldNames []string) string {
175182
message := fmt.Sprintf(`Cannot query field "%v" on type "%v".`, fieldName, ttypeName)
176183
const MaxLength = 5
177-
if len(suggestedTypes) > 0 {
184+
if len(suggestedTypeNames) > 0 {
178185
suggestions := ""
179-
if len(suggestedTypes) > MaxLength {
180-
suggestions = strings.Join(quoteStrings(suggestedTypes[0:MaxLength]), ", ") +
181-
fmt.Sprintf(`, and %v other types`, len(suggestedTypes)-MaxLength)
186+
if len(suggestedTypeNames) > MaxLength {
187+
suggestions = quotedOrList(suggestedTypeNames[0:MaxLength])
182188
} else {
183-
suggestions = strings.Join(quoteStrings(suggestedTypes), ", ")
189+
suggestions = quotedOrList(suggestedTypeNames)
184190
}
185-
message = fmt.Sprintf(`%v However, this field exists on %v. `+
186-
`Perhaps you meant to use an inline fragment?`, message, suggestions)
187-
}
188-
if len(suggestedFields) > 0 {
191+
message = fmt.Sprintf(`%v Did you mean to use an inline fragment on %v?`, message, suggestions)
192+
} else if len(suggestedFieldNames) > 0 {
189193
suggestions := ""
190-
if len(suggestedFields) > MaxLength {
191-
suggestions = strings.Join(quoteStrings(suggestedFields[0:MaxLength]), ", ") +
192-
fmt.Sprintf(`, or %v other field`, len(suggestedFields)-MaxLength)
194+
if len(suggestedFieldNames) > MaxLength {
195+
suggestions = quotedOrList(suggestedFieldNames[0:MaxLength])
193196
} else {
194-
suggestions = strings.Join(quoteStrings(suggestedFields), ", ")
197+
suggestions = quotedOrList(suggestedFieldNames)
195198
}
196-
message = fmt.Sprintf(`%v Did you mean to query %v?`, message, suggestions)
199+
message = fmt.Sprintf(`%v Did you mean %v?`, message, suggestions)
197200
}
198-
199201
return message
200202
}
201203

@@ -216,55 +218,23 @@ func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance
216218
if ttype != nil {
217219
fieldDef := context.FieldDef()
218220
if fieldDef == nil {
219-
// This isn't valid. Let's find suggestions, if any.
220-
suggestedTypes := []string{}
221-
221+
// This field doesn't exist, lets look for suggestions.
222222
nodeName := ""
223223
if node.Name != nil {
224224
nodeName = node.Name.Value
225225
}
226+
// First determine if there are any suggested types to condition on.
227+
suggestedTypeNames := getSuggestedTypeNames(context.Schema(), ttype, nodeName)
226228

227-
if ttype, ok := ttype.(Abstract); ok && IsAbstractType(ttype) {
228-
siblingInterfaces := getSiblingInterfacesIncludingField(context.Schema(), ttype, nodeName)
229-
implementations := getImplementationsIncludingField(context.Schema(), ttype, nodeName)
230-
suggestedMaps := map[string]bool{}
231-
for _, s := range siblingInterfaces {
232-
if _, ok := suggestedMaps[s]; !ok {
233-
suggestedMaps[s] = true
234-
suggestedTypes = append(suggestedTypes, s)
235-
}
236-
}
237-
for _, s := range implementations {
238-
if _, ok := suggestedMaps[s]; !ok {
239-
suggestedMaps[s] = true
240-
suggestedTypes = append(suggestedTypes, s)
241-
}
242-
}
243-
}
244-
229+
// If there are no suggested types, then perhaps this was a typo?
245230
suggestedFieldNames := []string{}
246-
suggestedFields := []string{}
247-
switch ttype := ttype.(type) {
248-
case *Object:
249-
for name := range ttype.Fields() {
250-
suggestedFieldNames = append(suggestedFieldNames, name)
251-
}
252-
suggestedFields = suggestionList(nodeName, suggestedFieldNames)
253-
case *Interface:
254-
for name := range ttype.Fields() {
255-
suggestedFieldNames = append(suggestedFieldNames, name)
256-
}
257-
suggestedFields = suggestionList(nodeName, suggestedFieldNames)
258-
case *InputObject:
259-
for name := range ttype.Fields() {
260-
suggestedFieldNames = append(suggestedFieldNames, name)
261-
}
262-
suggestedFields = suggestionList(nodeName, suggestedFieldNames)
231+
if len(suggestedTypeNames) == 0 {
232+
suggestedFieldNames = getSuggestedFieldNames(context.Schema(), ttype, nodeName)
263233
}
264234

265235
reportError(
266236
context,
267-
UndefinedFieldMessage(nodeName, ttype.Name(), suggestedTypes, suggestedFields),
237+
UndefinedFieldMessage(nodeName, ttype.Name(), suggestedTypeNames, suggestedFieldNames),
268238
[]ast.Node{node},
269239
)
270240
}
@@ -280,73 +250,100 @@ func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance
280250
}
281251
}
282252

283-
// Return implementations of `type` that include `fieldName` as a valid field.
284-
func getImplementationsIncludingField(schema *Schema, ttype Abstract, fieldName string) []string {
253+
// getSuggestedTypeNames Go through all of the implementations of type, as well as the interfaces
254+
// that they implement. If any of those types include the provided field,
255+
// suggest them, sorted by how often the type is referenced, starting
256+
// with Interfaces.
257+
func getSuggestedTypeNames(schema *Schema, ttype Output, fieldName string) []string {
285258

286-
result := []string{}
287-
for _, t := range schema.PossibleTypes(ttype) {
288-
fields := t.Fields()
289-
if _, ok := fields[fieldName]; ok {
290-
result = append(result, fmt.Sprintf(`%v`, t.Name()))
291-
}
292-
}
293-
294-
sort.Strings(result)
295-
return result
296-
}
297-
298-
// Go through all of the implementations of type, and find other interaces
299-
// that they implement. If those interfaces include `field` as a valid field,
300-
// return them, sorted by how often the implementations include the other
301-
// interface.
302-
func getSiblingInterfacesIncludingField(schema *Schema, ttype Abstract, fieldName string) []string {
303-
implementingObjects := schema.PossibleTypes(ttype)
304-
305-
result := []string{}
306-
suggestedInterfaceSlice := []*suggestedInterface{}
259+
possibleTypes := schema.PossibleTypes(ttype)
307260

308-
// stores a map of interface name => index in suggestedInterfaceSlice
261+
suggestedObjectTypes := []string{}
262+
suggestedInterfaces := []*suggestedInterface{}
263+
// stores a map of interface name => index in suggestedInterfaces
309264
suggestedInterfaceMap := map[string]int{}
265+
// stores a maps of object name => true to remove duplicates from results
266+
suggestedObjectMap := map[string]bool{}
310267

311-
for _, t := range implementingObjects {
312-
for _, i := range t.Interfaces() {
313-
if i == nil {
314-
continue
315-
}
316-
fields := i.Fields()
317-
if _, ok := fields[fieldName]; !ok {
268+
for _, possibleType := range possibleTypes {
269+
if field, ok := possibleType.Fields()[fieldName]; !ok || field == nil {
270+
continue
271+
}
272+
// This object type defines this field.
273+
suggestedObjectTypes = append(suggestedObjectTypes, possibleType.Name())
274+
suggestedObjectMap[possibleType.Name()] = true
275+
276+
for _, possibleInterface := range possibleType.Interfaces() {
277+
if field, ok := possibleInterface.Fields()[fieldName]; !ok || field == nil {
318278
continue
319279
}
320-
index, ok := suggestedInterfaceMap[i.Name()]
280+
281+
// This interface type defines this field.
282+
283+
// - find the index of the suggestedInterface and retrieving the interface
284+
// - increase count
285+
index, ok := suggestedInterfaceMap[possibleInterface.Name()]
321286
if !ok {
322-
suggestedInterfaceSlice = append(suggestedInterfaceSlice, &suggestedInterface{
323-
name: i.Name(),
287+
suggestedInterfaces = append(suggestedInterfaces, &suggestedInterface{
288+
name: possibleInterface.Name(),
324289
count: 0,
325290
})
326-
index = len(suggestedInterfaceSlice) - 1
291+
index = len(suggestedInterfaces) - 1
292+
suggestedInterfaceMap[possibleInterface.Name()] = index
327293
}
328-
if index < len(suggestedInterfaceSlice) {
329-
s := suggestedInterfaceSlice[index]
330-
if s.name == i.Name() {
294+
if index < len(suggestedInterfaces) {
295+
s := suggestedInterfaces[index]
296+
if s.name == possibleInterface.Name() {
331297
s.count = s.count + 1
332298
}
333299
}
334300
}
335301
}
336-
sort.Sort(suggestedInterfaceSortedSlice(suggestedInterfaceSlice))
337302

338-
for _, s := range suggestedInterfaceSlice {
339-
result = append(result, fmt.Sprintf(`%v`, s.name))
303+
// sort results (by count usage for interfaces, alphabetical order for objects)
304+
sort.Sort(suggestedInterfaceSortedSlice(suggestedInterfaces))
305+
sort.Sort(sort.StringSlice(suggestedObjectTypes))
306+
307+
// return concatenated slices of both interface and object type names
308+
// and removing duplicates
309+
// ordered by: interface (sorted) and object (sorted)
310+
results := []string{}
311+
for _, s := range suggestedInterfaces {
312+
if _, ok := suggestedObjectMap[s.name]; !ok {
313+
results = append(results, s.name)
314+
315+
}
340316
}
341-
return result
317+
results = append(results, suggestedObjectTypes...)
318+
return results
319+
}
320+
321+
// getSuggestedFieldNames For the field name provided, determine if there are any similar field names
322+
// that may be the result of a typo.
323+
func getSuggestedFieldNames(schema *Schema, ttype Output, fieldName string) []string {
342324

325+
fields := FieldDefinitionMap{}
326+
switch ttype := ttype.(type) {
327+
case *Object:
328+
fields = ttype.Fields()
329+
case *Interface:
330+
fields = ttype.Fields()
331+
default:
332+
return []string{}
333+
}
334+
335+
possibleFieldNames := []string{}
336+
for possibleFieldName := range fields {
337+
possibleFieldNames = append(possibleFieldNames, possibleFieldName)
338+
}
339+
return suggestionList(fieldName, possibleFieldNames)
343340
}
344341

342+
// suggestedInterface an internal struct to sort interface by usage count
345343
type suggestedInterface struct {
346344
name string
347345
count int
348346
}
349-
350347
type suggestedInterfaceSortedSlice []*suggestedInterface
351348

352349
func (s suggestedInterfaceSortedSlice) Len() int {
@@ -356,7 +353,10 @@ func (s suggestedInterfaceSortedSlice) Swap(i, j int) {
356353
s[i], s[j] = s[j], s[i]
357354
}
358355
func (s suggestedInterfaceSortedSlice) Less(i, j int) bool {
359-
return s[i].count < s[j].count
356+
if s[i].count == s[j].count {
357+
return s[i].name < s[j].name
358+
}
359+
return s[i].count > s[j].count
360360
}
361361

362362
// FragmentsOnCompositeTypesRule Fragments on composite type

0 commit comments

Comments
 (0)