Skip to content

Commit f6f1c9d

Browse files
authored
Merge pull request #18 from vektah/values-of-correct-type
Implement ValuesOfCorrectType validator
2 parents f8faf60 + d13dcfb commit f6f1c9d

11 files changed

Lines changed: 327 additions & 52 deletions

.gometalinter.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
"PartitionStrategy": "packages"
1010
}
1111
},
12-
"Disable": ["golint","gocyclo", "goconst", "gas"]
13-
}
12+
"Disable": ["golint","gocyclo", "goconst", "gas", "interfacer"]
13+
}

ast.go

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package gqlparser
22

3+
import "strconv"
4+
35
type Operation string
46

57
const (
@@ -132,19 +134,67 @@ type FragmentDefinition struct {
132134
// Values
133135

134136
type Value interface {
135-
isValue()
137+
Value(vars map[Variable]interface{}) (interface{}, error)
138+
String() string
139+
}
140+
141+
func (v Variable) Value(vars map[Variable]interface{}) (interface{}, error) {
142+
return vars[v], nil
143+
}
144+
func (v IntValue) Value(vars map[Variable]interface{}) (interface{}, error) {
145+
return strconv.ParseInt(string(v), 10, 64)
146+
}
147+
func (v FloatValue) Value(vars map[Variable]interface{}) (interface{}, error) {
148+
return strconv.ParseFloat(string(v), 64)
149+
}
150+
func (v StringValue) Value(vars map[Variable]interface{}) (interface{}, error) {
151+
return string(v), nil
152+
}
153+
func (v BlockValue) Value(vars map[Variable]interface{}) (interface{}, error) {
154+
return string(v), nil
155+
}
156+
func (v BooleanValue) Value(vars map[Variable]interface{}) (interface{}, error) {
157+
return bool(v), nil
158+
}
159+
func (v NullValue) Value(vars map[Variable]interface{}) (interface{}, error) {
160+
return nil, nil
161+
}
162+
func (v EnumValue) Value(vars map[Variable]interface{}) (interface{}, error) {
163+
return string(v), nil
164+
}
165+
func (v ListValue) Value(vars map[Variable]interface{}) (interface{}, error) {
166+
var val []interface{}
167+
for _, elem := range v {
168+
elemVal, err := elem.Value(vars)
169+
if err != nil {
170+
return val, err
171+
}
172+
val = append(val, elemVal)
173+
}
174+
return val, nil
175+
}
176+
func (v ObjectValue) Value(vars map[Variable]interface{}) (interface{}, error) {
177+
val := map[string]interface{}{}
178+
for _, elem := range v {
179+
elemVal, err := elem.Value.Value(vars)
180+
if err != nil {
181+
return val, err
182+
}
183+
val[elem.Name] = elemVal
184+
}
185+
return val, nil
136186
}
137187

138-
func (Variable) isValue() {}
139-
func (IntValue) isValue() {}
140-
func (FloatValue) isValue() {}
141-
func (StringValue) isValue() {}
142-
func (BlockValue) isValue() {}
143-
func (BooleanValue) isValue() {}
144-
func (NullValue) isValue() {}
145-
func (EnumValue) isValue() {}
146-
func (ListValue) isValue() {}
147-
func (ObjectValue) isValue() {}
188+
func (v Variable) String() string { return string(v) }
189+
func (v IntValue) String() string { return string(v) }
190+
func (v FloatValue) String() string { return string(v) }
191+
func (v StringValue) String() string { return strconv.Quote(string(v)) }
192+
func (v BlockValue) String() string { return strconv.Quote(string(v)) }
193+
func (v BooleanValue) String() string { return strconv.FormatBool(bool(v)) }
194+
func (v NullValue) String() string { return "null" }
195+
func (v EnumValue) String() string { return string(v) }
196+
func (v ListValue) String() string { return "list" }
197+
func (v ObjectValue) String() string { return "object" }
148198

149199
type IntValue string
150200
type FloatValue string
@@ -161,6 +211,15 @@ type ObjectField struct {
161211
Value Value
162212
}
163213

214+
func (o ObjectValue) Find(name string) Value {
215+
for _, f := range o {
216+
if f.Name == name {
217+
return f.Value
218+
}
219+
}
220+
return nil
221+
}
222+
164223
// Directives
165224

166225
type Directive struct {
@@ -173,6 +232,7 @@ type Directive struct {
173232
type Type interface {
174233
Name() string
175234
String() string
235+
IsRequired() bool
176236
}
177237

178238
func (t NamedType) Name() string { return string(t) }
@@ -183,6 +243,10 @@ func (t NamedType) String() string { return string(t) }
183243
func (t ListType) String() string { return "[" + t.Type.Name() + "]" }
184244
func (t NonNullType) String() string { return t.Type.Name() + "!" }
185245

246+
func (t NamedType) IsRequired() bool { return false }
247+
func (t ListType) IsRequired() bool { return false }
248+
func (t NonNullType) IsRequired() bool { return true }
249+
186250
type NamedType string
187251

188252
type ListType struct {
@@ -243,6 +307,15 @@ func (d *Definition) Field(name string) *FieldDefinition {
243307
return nil
244308
}
245309

310+
func (d *Definition) EnumValue(name string) *EnumValueDefinition {
311+
for _, e := range d.Values {
312+
if e.Name == name {
313+
return &e
314+
}
315+
}
316+
return nil
317+
}
318+
246319
func (d *Definition) IsLeafType() bool {
247320
return d.Kind == Enum || d.Kind == Scalar
248321
}
@@ -255,6 +328,15 @@ func (d *Definition) IsCompositeType() bool {
255328
return d.Kind == Object || d.Kind == Interface || d.Kind == Union
256329
}
257330

331+
func (d *Definition) OneOf(types ...string) bool {
332+
for _, t := range types {
333+
if d.Name == t {
334+
return true
335+
}
336+
}
337+
return false
338+
}
339+
258340
type FieldDefinition struct {
259341
Description string
260342
Name string

loader.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func LoadSchema(input string) (*Schema, error) {
6464
FieldDefinition{
6565
Name: "if",
6666
Description: "Skipped when true.",
67-
Type: NamedType("Boolean"),
67+
Type: NonNullType{NamedType("Boolean")},
6868
},
6969
},
7070
Locations: []DirectiveLocation{LocationField, LocationFragmentSpread, LocationInlineFragment},
@@ -76,7 +76,7 @@ func LoadSchema(input string) (*Schema, error) {
7676
FieldDefinition{
7777
Name: "if",
7878
Description: "Included when true.",
79-
Type: NamedType("Boolean"),
79+
Type: NonNullType{NamedType("Boolean")},
8080
},
8181
},
8282
Locations: []DirectiveLocation{LocationField, LocationFragmentSpread, LocationInlineFragment},

spec/validation/deviations.yml

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1-
# The order here has switched due to internal differences between the js parser and gqlparser
2-
'Validate: Fields on correct type/Defined on implementors but not on interface':
1+
- rule: 'Validate: Fields on correct type/Defined on implementors but not on interface'
32
errors:
43
- message: Cannot query field "nickname" on type "Pet". Did you mean to use an inline fragment on "Cat" or "Dog"?
54
locations:
65
- {line: 3, column: 9}
76

8-
'Validate: Known directives/within schema language/with well placed directives':
7+
- rule: 'Validate: Known directives/within schema language/with well placed directives'
98
skip: true
10-
'Validate: Known directives/within schema language/with misplaced directives':
9+
10+
- rule: 'Validate: Known directives/within schema language/with misplaced directives'
1111
skip: true
12-
'Validate: Known type names/ignores type definitions':
12+
13+
- rule: 'Validate: Known type names/ignores type definitions'
1314
skip: true
14-
'Validate: Overlapping fields can be merged/return types must be unambiguous/reports correctly when a non-exclusive follows an exclusive':
15+
16+
- rule: 'Validate: Overlapping fields can be merged/return types must be unambiguous/reports correctly when a non-exclusive follows an exclusive'
1517
skip: true
16-
'Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing subfields':
18+
19+
- rule: 'Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing subfields'
1720
skip: true
21+
22+
- rule: 'Validate: Values of correct type/.*custom scalar.*'
23+
skip: "Custom scalars are a runtime feature, maybe they dont belong in here?"
24+
25+
- rule: 'Validate: Values of correct type/.*Variable default values.*'
26+
skip: "TODO"

validator/error.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,24 @@ type ErrorOption func(err *errors.Validation)
1010

1111
func Message(msg string, args ...interface{}) ErrorOption {
1212
return func(err *errors.Validation) {
13-
err.Message = fmt.Sprintf(msg, args...)
13+
err.Message += fmt.Sprintf(msg, args...)
1414
}
1515
}
1616

17-
func SuggestList(typed string, suggestions []string) ErrorOption {
17+
func SuggestListQuoted(prefix string, typed string, suggestions []string) ErrorOption {
1818
suggested := suggestionList(typed, suggestions)
1919
return func(err *errors.Validation) {
2020
if len(suggested) > 0 {
21-
err.Message += " Did you mean " + quotedOrList(suggested...) + "?"
21+
err.Message += " " + prefix + " " + quotedOrList(suggested...) + "?"
22+
}
23+
}
24+
}
25+
26+
func SuggestListUnquoted(prefix string, typed string, suggestions []string) ErrorOption {
27+
suggested := suggestionList(typed, suggestions)
28+
return func(err *errors.Validation) {
29+
if len(suggested) > 0 {
30+
err.Message += " " + prefix + " " + orList(suggested...) + "?"
2231
}
2332
}
2433
}

validator/known_argument_names.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func init() {
2424

2525
addError(
2626
Message(`Unknown argument "%s" on field "%s" of type "%s".`, arg.Name, field.Name, parentDef.Name),
27-
SuggestList(arg.Name, suggestions),
27+
SuggestListQuoted("Did you mean", arg.Name, suggestions),
2828
)
2929
}
3030
})
@@ -46,7 +46,7 @@ func init() {
4646

4747
addError(
4848
Message(`Unknown argument "%s" on directive "@%s".`, arg.Name, directive.Name),
49-
SuggestList(arg.Name, suggestions),
49+
SuggestListQuoted("Did you mean", arg.Name, suggestions),
5050
)
5151
}
5252
})

validator/known_type_names.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func init() {
4848
possibleTypes = append(possibleTypes, t.Name)
4949
}
5050

51-
list := SuggestList(typeName, possibleTypes)
51+
list := SuggestListQuoted("Did you mean", typeName, possibleTypes)
5252

5353
addError(
5454
Message(`Unknown type "%s".`, typeName),

validator/unique_input_field_names.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66

77
func init() {
88
addRule("UniqueInputFieldNames", func(observers *Events, addError addErrFunc) {
9-
observers.OnValue(func(walker *Walker, value gqlparser.Value) {
9+
observers.OnValue(func(walker *Walker, fieldType gqlparser.Type, def *gqlparser.Definition, value gqlparser.Value) {
1010
object, isObject := value.(gqlparser.ObjectValue)
1111
if !isObject {
1212
return

validator/validate_test.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package validator
33
import (
44
"fmt"
55
"io/ioutil"
6+
"regexp"
7+
"sort"
68
"strings"
79
"testing"
810

@@ -19,15 +21,25 @@ type Spec struct {
1921
Schema int
2022
Query string
2123
Errors []errors.Validation
22-
Skip bool
24+
}
25+
26+
type Deviation struct {
27+
Rule string
28+
Errors []errors.Validation
29+
Skip string
30+
31+
pattern *regexp.Regexp
2332
}
2433

2534
func TestSpec(t *testing.T) {
2635
var rawSchemas []string
2736
readYaml("../spec/validation/schemas.yml", &rawSchemas)
2837

29-
var deviations map[string]*Spec
38+
var deviations []*Deviation
3039
readYaml("../spec/validation/deviations.yml", &deviations)
40+
for _, d := range deviations {
41+
d.pattern = regexp.MustCompile("^" + d.Rule + "$")
42+
}
3143

3244
var schemas []*gqlparser.Schema
3345
for _, schema := range rawSchemas {
@@ -54,7 +66,6 @@ func TestSpec(t *testing.T) {
5466
"OverlappingFieldsCanBeMerged",
5567
"PossibleFragmentSpreads",
5668
"ProvidedRequiredArguments",
57-
"ValuesOfCorrectType",
5869
"VariablesAreInputTypes",
5970
"VariablesInAllowedPosition",
6071
}
@@ -88,7 +99,7 @@ file:
8899
}
89100
}
90101

91-
func runSpec(schemas []*gqlparser.Schema, deviations map[string]*Spec, filename string) func(t *testing.T) {
102+
func runSpec(schemas []*gqlparser.Schema, deviations []*Deviation, filename string) func(t *testing.T) {
92103
var specs []Spec
93104
readYaml(filename, &specs)
94105
return func(t *testing.T) {
@@ -97,12 +108,14 @@ func runSpec(schemas []*gqlparser.Schema, deviations map[string]*Spec, filename
97108
spec.Errors = nil
98109
}
99110
t.Run(spec.Name, func(t *testing.T) {
100-
if deviation := deviations[spec.Name]; deviation != nil {
101-
if deviation.Errors != nil {
102-
spec.Errors = deviation.Errors
103-
}
104-
if deviation.Skip {
105-
t.SkipNow()
111+
for _, deviation := range deviations {
112+
if deviation.pattern.MatchString(spec.Name) {
113+
if deviation.Skip != "" {
114+
t.Skip(deviation.Skip)
115+
}
116+
if deviation.Errors != nil {
117+
spec.Errors = deviation.Errors
118+
}
106119
}
107120
}
108121

@@ -123,7 +136,16 @@ func runSpec(schemas []*gqlparser.Schema, deviations map[string]*Spec, filename
123136
for i := range spec.Errors {
124137
spec.Errors[i].Locations = nil
125138
spec.Errors[i].Rule = spec.Rule
139+
140+
// remove inconsistent use of ;
141+
spec.Errors[i].Message = strings.Replace(spec.Errors[i].Message, "; Did you mean", ". Did you mean", -1)
126142
}
143+
sort.Slice(spec.Errors, func(i, j int) bool {
144+
return strings.Compare(spec.Errors[i].Message, spec.Errors[j].Message) > 0
145+
})
146+
sort.Slice(finalErrors, func(i, j int) bool {
147+
return strings.Compare(finalErrors[i].Message, finalErrors[j].Message) > 0
148+
})
127149
assert.Equal(t, spec.Errors, finalErrors)
128150

129151
if t.Failed() {

0 commit comments

Comments
 (0)