@@ -2,14 +2,15 @@ package graphql
2
2
3
3
import (
4
4
"fmt"
5
+ "math"
6
+ "sort"
7
+ "strings"
8
+
5
9
"github.com/graphql-go/graphql/gqlerrors"
6
10
"github.com/graphql-go/graphql/language/ast"
7
11
"github.com/graphql-go/graphql/language/kinds"
8
12
"github.com/graphql-go/graphql/language/printer"
9
13
"github.com/graphql-go/graphql/language/visitor"
10
- "math"
11
- "sort"
12
- "strings"
13
14
)
14
15
15
16
// SpecifiedRules set includes all validation rules defined by the GraphQL spec.
@@ -170,32 +171,33 @@ func quoteStrings(slice []string) []string {
170
171
}
171
172
return quoted
172
173
}
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 {
175
182
message := fmt .Sprintf (`Cannot query field "%v" on type "%v".` , fieldName , ttypeName )
176
183
const MaxLength = 5
177
- if len (suggestedTypes ) > 0 {
184
+ if len (suggestedTypeNames ) > 0 {
178
185
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 ])
182
188
} else {
183
- suggestions = strings . Join ( quoteStrings ( suggestedTypes ), ", " )
189
+ suggestions = quotedOrList ( suggestedTypeNames )
184
190
}
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 {
189
193
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 ])
193
196
} else {
194
- suggestions = strings . Join ( quoteStrings ( suggestedFields ), ", " )
197
+ suggestions = quotedOrList ( suggestedFieldNames )
195
198
}
196
- message = fmt .Sprintf (`%v Did you mean to query %v?` , message , suggestions )
199
+ message = fmt .Sprintf (`%v Did you mean %v?` , message , suggestions )
197
200
}
198
-
199
201
return message
200
202
}
201
203
@@ -216,55 +218,23 @@ func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance
216
218
if ttype != nil {
217
219
fieldDef := context .FieldDef ()
218
220
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.
222
222
nodeName := ""
223
223
if node .Name != nil {
224
224
nodeName = node .Name .Value
225
225
}
226
+ // First determine if there are any suggested types to condition on.
227
+ suggestedTypeNames := getSuggestedTypeNames (context .Schema (), ttype , nodeName )
226
228
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?
245
230
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 )
263
233
}
264
234
265
235
reportError (
266
236
context ,
267
- UndefinedFieldMessage (nodeName , ttype .Name (), suggestedTypes , suggestedFields ),
237
+ UndefinedFieldMessage (nodeName , ttype .Name (), suggestedTypeNames , suggestedFieldNames ),
268
238
[]ast.Node {node },
269
239
)
270
240
}
@@ -280,73 +250,100 @@ func FieldsOnCorrectTypeRule(context *ValidationContext) *ValidationRuleInstance
280
250
}
281
251
}
282
252
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 {
285
258
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 )
307
260
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
309
264
suggestedInterfaceMap := map [string ]int {}
265
+ // stores a maps of object name => true to remove duplicates from results
266
+ suggestedObjectMap := map [string ]bool {}
310
267
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 {
318
278
continue
319
279
}
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 ()]
321
286
if ! ok {
322
- suggestedInterfaceSlice = append (suggestedInterfaceSlice , & suggestedInterface {
323
- name : i .Name (),
287
+ suggestedInterfaces = append (suggestedInterfaces , & suggestedInterface {
288
+ name : possibleInterface .Name (),
324
289
count : 0 ,
325
290
})
326
- index = len (suggestedInterfaceSlice ) - 1
291
+ index = len (suggestedInterfaces ) - 1
292
+ suggestedInterfaceMap [possibleInterface .Name ()] = index
327
293
}
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 () {
331
297
s .count = s .count + 1
332
298
}
333
299
}
334
300
}
335
301
}
336
- sort .Sort (suggestedInterfaceSortedSlice (suggestedInterfaceSlice ))
337
302
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
+ }
340
316
}
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 {
342
324
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 )
343
340
}
344
341
342
+ // suggestedInterface an internal struct to sort interface by usage count
345
343
type suggestedInterface struct {
346
344
name string
347
345
count int
348
346
}
349
-
350
347
type suggestedInterfaceSortedSlice []* suggestedInterface
351
348
352
349
func (s suggestedInterfaceSortedSlice ) Len () int {
@@ -356,7 +353,10 @@ func (s suggestedInterfaceSortedSlice) Swap(i, j int) {
356
353
s [i ], s [j ] = s [j ], s [i ]
357
354
}
358
355
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
360
360
}
361
361
362
362
// FragmentsOnCompositeTypesRule Fragments on composite type
0 commit comments