Skip to content

Commit

Permalink
validation: fields
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Mar 20, 2017
1 parent c387449 commit 95a4ecd
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 33 deletions.
30 changes: 17 additions & 13 deletions internal/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type Field struct {
Arguments common.ArgumentList
Directives map[string]common.ArgumentList
SelSet *SelectionSet
Location *errors.Location
}

type FragmentSpread struct {
Expand Down Expand Up @@ -210,7 +211,9 @@ func parseSelection(l *lexer.Lexer) Selection {
}

func parseField(l *lexer.Lexer) *Field {
f := &Field{}
f := &Field{
Location: l.Location(),
}
f.Alias = l.ConsumeIdent()
f.Name = f.Alias
if l.Peek() == ':' {
Expand All @@ -231,19 +234,20 @@ func parseSpread(l *lexer.Lexer) Selection {
l.ConsumeToken('.')
l.ConsumeToken('.')
l.ConsumeToken('.')
ident := l.ConsumeIdent()

if ident == "on" {
f := &Fragment{}
f := &Fragment{}
if l.Peek() == scanner.Ident {
ident := l.ConsumeIdent()
if ident != "on" {
fs := &FragmentSpread{
Name: ident,
}
fs.Directives = common.ParseDirectives(l)
return fs
}
f.On = l.ConsumeIdent()
f.Directives = common.ParseDirectives(l)
f.SelSet = parseSelectionSet(l)
return f
}

fs := &FragmentSpread{
Name: ident,
}
fs.Directives = common.ParseDirectives(l)
return fs
f.Directives = common.ParseDirectives(l)
f.SelSet = parseSelectionSet(l)
return f
}
8 changes: 8 additions & 0 deletions internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ func (l FieldList) Get(name string) *Field {
return nil
}

func (l FieldList) Names() []string {
names := make([]string, len(l))
for i, f := range l {
names[i] = f.Name
}
return names
}

type Directive struct {
Name string
Desc string
Expand Down
2 changes: 1 addition & 1 deletion internal/tests/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestAll(t *testing.T) {
t.Run(test.Name, func(t *testing.T) {
d, err := query.Parse(test.Query)
if err != nil {
t.Error(err)
t.Fatal(err)
}
got := validation.Validate(s, d)
if got == nil {
Expand Down
214 changes: 214 additions & 0 deletions internal/tests/testdata/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -839,5 +839,219 @@
]
}
]
},
{
"name": "Validate: Fields on correct type/Object field selection",
"query": "\n fragment objectFieldSelection on Dog {\n __typename\n name\n }\n ",
"errors": []
},
{
"name": "Validate: Fields on correct type/Aliased object field selection",
"query": "\n fragment aliasedObjectFieldSelection on Dog {\n tn : __typename\n otherName : name\n }\n ",
"errors": []
},
{
"name": "Validate: Fields on correct type/Interface field selection",
"query": "\n fragment interfaceFieldSelection on Pet {\n __typename\n name\n }\n ",
"errors": []
},
{
"name": "Validate: Fields on correct type/Aliased interface field selection",
"query": "\n fragment interfaceFieldSelection on Pet {\n otherName : name\n }\n ",
"errors": []
},
{
"name": "Validate: Fields on correct type/Lying alias selection",
"query": "\n fragment lyingAliasSelection on Dog {\n name : nickname\n }\n ",
"errors": []
},
{
"name": "Validate: Fields on correct type/Ignores fields on unknown type",
"query": "\n fragment unknownSelection on UnknownType {\n unknownField\n }\n ",
"errors": []
},
{
"name": "Validate: Fields on correct type/reports errors when type is known again",
"query": "\n fragment typeKnownAgain on Pet {\n unknown_pet_field {\n ... on Cat {\n unknown_cat_field\n }\n }\n }",
"errors": [
{
"message": "Cannot query field \"unknown_pet_field\" on type \"Pet\".",
"locations": [
{
"line": 3,
"column": 9
}
]
},
{
"message": "Cannot query field \"unknown_cat_field\" on type \"Cat\".",
"locations": [
{
"line": 5,
"column": 13
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Field not defined on fragment",
"query": "\n fragment fieldNotDefined on Dog {\n meowVolume\n }",
"errors": [
{
"message": "Cannot query field \"meowVolume\" on type \"Dog\". Did you mean \"barkVolume\"?",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Ignores deeply unknown field",
"query": "\n fragment deepFieldNotDefined on Dog {\n unknown_field {\n deeper_unknown_field\n }\n }",
"errors": [
{
"message": "Cannot query field \"unknown_field\" on type \"Dog\".",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Sub-field not defined",
"query": "\n fragment subFieldNotDefined on Human {\n pets {\n unknown_field\n }\n }",
"errors": [
{
"message": "Cannot query field \"unknown_field\" on type \"Pet\".",
"locations": [
{
"line": 4,
"column": 11
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Field not defined on inline fragment",
"query": "\n fragment fieldNotDefined on Pet {\n ... on Dog {\n meowVolume\n }\n }",
"errors": [
{
"message": "Cannot query field \"meowVolume\" on type \"Dog\". Did you mean \"barkVolume\"?",
"locations": [
{
"line": 4,
"column": 11
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Aliased field target not defined",
"query": "\n fragment aliasedFieldTargetNotDefined on Dog {\n volume : mooVolume\n }",
"errors": [
{
"message": "Cannot query field \"mooVolume\" on type \"Dog\". Did you mean \"barkVolume\"?",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Aliased lying field target not defined",
"query": "\n fragment aliasedLyingFieldTargetNotDefined on Dog {\n barkVolume : kawVolume\n }",
"errors": [
{
"message": "Cannot query field \"kawVolume\" on type \"Dog\". Did you mean \"barkVolume\"?",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Not defined on interface",
"query": "\n fragment notDefinedOnInterface on Pet {\n tailLength\n }",
"errors": [
{
"message": "Cannot query field \"tailLength\" on type \"Pet\".",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Defined on implementors but not on interface",
"query": "\n fragment definedOnImplementorsButNotInterface on Pet {\n nickname\n }",
"errors": [
{
"message": "Cannot query field \"nickname\" on type \"Pet\".",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Meta field selection on union",
"query": "\n fragment directFieldSelectionOnUnion on CatOrDog {\n __typename\n }",
"errors": []
},
{
"name": "Validate: Fields on correct type/Direct field selection on union",
"query": "\n fragment directFieldSelectionOnUnion on CatOrDog {\n directField\n }",
"errors": [
{
"message": "Cannot query field \"directField\" on type \"CatOrDog\".",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/Defined on implementors queried on union",
"query": "\n fragment definedOnImplementorsQueriedOnUnion on CatOrDog {\n name\n }",
"errors": [
{
"message": "Cannot query field \"name\" on type \"CatOrDog\".",
"locations": [
{
"line": 3,
"column": 9
}
]
}
]
},
{
"name": "Validate: Fields on correct type/valid field in inline fragment",
"query": "\n fragment objectFieldSelection on Pet {\n ... on Dog {\n name\n }\n ... {\n name\n }\n }\n ",
"errors": []
}
]
71 changes: 71 additions & 0 deletions internal/validation/suggestion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package validation

import (
"fmt"
"sort"
"strconv"
"strings"
)

func makeSuggestion(prefix string, options []string, input string) string {
var selected []string
distances := make(map[string]int)
for _, opt := range options {
distance := levenshteinDistance(input, opt)
threshold := max(len(input)/2, max(len(opt)/2, 1))
if distance < threshold {
selected = append(selected, opt)
distances[opt] = distance
}
}

if len(selected) == 0 {
return ""
}
sort.Slice(selected, func(i, j int) bool {
return distances[selected[i]] < distances[selected[j]]
})

parts := make([]string, len(selected))
for i, opt := range selected {
parts[i] = strconv.Quote(opt)
}
if len(parts) > 1 {
parts[len(parts)-1] = "or " + parts[len(parts)-1]
}
return fmt.Sprintf(" %s %s?", prefix, strings.Join(parts, ", "))
}

func levenshteinDistance(s1, s2 string) int {
column := make([]int, len(s1)+1)
for y := range s1 {
column[y+1] = y + 1
}
for x, rx := range s2 {
column[0] = x + 1
lastdiag := x
for y, ry := range s1 {
olddiag := column[y+1]
if rx != ry {
lastdiag++
}
column[y+1] = min(column[y+1]+1, min(column[y]+1, lastdiag))
lastdiag = olddiag
}
}
return column[len(s1)]
}

func min(a, b int) int {
if a < b {
return a
}
return b
}

func max(a, b int) int {
if a > b {
return a
}
return b
}
Loading

0 comments on commit 95a4ecd

Please sign in to comment.