Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gometalinter.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"PartitionStrategy": "packages"
}
},
"Disable": ["golint","gocyclo", "goconst", "gas"]
}
"Disable": ["golint","gocyclo", "goconst", "gas", "interfacer"]
}
104 changes: 93 additions & 11 deletions ast.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package gqlparser

import "strconv"

type Operation string

const (
Expand Down Expand Up @@ -132,19 +134,67 @@ type FragmentDefinition struct {
// Values

type Value interface {
isValue()
Value(vars map[Variable]interface{}) (interface{}, error)
String() string
}

func (v Variable) Value(vars map[Variable]interface{}) (interface{}, error) {
return vars[v], nil
}
func (v IntValue) Value(vars map[Variable]interface{}) (interface{}, error) {
return strconv.ParseInt(string(v), 10, 64)
}
func (v FloatValue) Value(vars map[Variable]interface{}) (interface{}, error) {
return strconv.ParseFloat(string(v), 64)
}
func (v StringValue) Value(vars map[Variable]interface{}) (interface{}, error) {
return string(v), nil
}
func (v BlockValue) Value(vars map[Variable]interface{}) (interface{}, error) {
return string(v), nil
}
func (v BooleanValue) Value(vars map[Variable]interface{}) (interface{}, error) {
return bool(v), nil
}
func (v NullValue) Value(vars map[Variable]interface{}) (interface{}, error) {
return nil, nil
}
func (v EnumValue) Value(vars map[Variable]interface{}) (interface{}, error) {
return string(v), nil
}
func (v ListValue) Value(vars map[Variable]interface{}) (interface{}, error) {
var val []interface{}
for _, elem := range v {
elemVal, err := elem.Value(vars)
if err != nil {
return val, err
}
val = append(val, elemVal)
}
return val, nil
}
func (v ObjectValue) Value(vars map[Variable]interface{}) (interface{}, error) {
val := map[string]interface{}{}
for _, elem := range v {
elemVal, err := elem.Value.Value(vars)
if err != nil {
return val, err
}
val[elem.Name] = elemVal
}
return val, nil
}

func (Variable) isValue() {}
func (IntValue) isValue() {}
func (FloatValue) isValue() {}
func (StringValue) isValue() {}
func (BlockValue) isValue() {}
func (BooleanValue) isValue() {}
func (NullValue) isValue() {}
func (EnumValue) isValue() {}
func (ListValue) isValue() {}
func (ObjectValue) isValue() {}
func (v Variable) String() string { return string(v) }
func (v IntValue) String() string { return string(v) }
func (v FloatValue) String() string { return string(v) }
func (v StringValue) String() string { return strconv.Quote(string(v)) }
func (v BlockValue) String() string { return strconv.Quote(string(v)) }
func (v BooleanValue) String() string { return strconv.FormatBool(bool(v)) }
func (v NullValue) String() string { return "null" }
func (v EnumValue) String() string { return string(v) }
func (v ListValue) String() string { return "list" }
func (v ObjectValue) String() string { return "object" }

type IntValue string
type FloatValue string
Expand All @@ -161,6 +211,15 @@ type ObjectField struct {
Value Value
}

func (o ObjectValue) Find(name string) Value {
for _, f := range o {
if f.Name == name {
return f.Value
}
}
return nil
}

// Directives

type Directive struct {
Expand All @@ -173,6 +232,7 @@ type Directive struct {
type Type interface {
Name() string
String() string
IsRequired() bool
}

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

func (t NamedType) IsRequired() bool { return false }
func (t ListType) IsRequired() bool { return false }
func (t NonNullType) IsRequired() bool { return true }

type NamedType string

type ListType struct {
Expand Down Expand Up @@ -243,6 +307,15 @@ func (d *Definition) Field(name string) *FieldDefinition {
return nil
}

func (d *Definition) EnumValue(name string) *EnumValueDefinition {
for _, e := range d.Values {
if e.Name == name {
return &e
}
}
return nil
}

func (d *Definition) IsLeafType() bool {
return d.Kind == Enum || d.Kind == Scalar
}
Expand All @@ -255,6 +328,15 @@ func (d *Definition) IsCompositeType() bool {
return d.Kind == Object || d.Kind == Interface || d.Kind == Union
}

func (d *Definition) OneOf(types ...string) bool {
for _, t := range types {
if d.Name == t {
return true
}
}
return false
}

type FieldDefinition struct {
Description string
Name string
Expand Down
4 changes: 2 additions & 2 deletions loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func LoadSchema(input string) (*Schema, error) {
FieldDefinition{
Name: "if",
Description: "Skipped when true.",
Type: NamedType("Boolean"),
Type: NonNullType{NamedType("Boolean")},
},
},
Locations: []DirectiveLocation{LocationField, LocationFragmentSpread, LocationInlineFragment},
Expand All @@ -76,7 +76,7 @@ func LoadSchema(input string) (*Schema, error) {
FieldDefinition{
Name: "if",
Description: "Included when true.",
Type: NamedType("Boolean"),
Type: NonNullType{NamedType("Boolean")},
},
},
Locations: []DirectiveLocation{LocationField, LocationFragmentSpread, LocationInlineFragment},
Expand Down
23 changes: 16 additions & 7 deletions spec/validation/deviations.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
# The order here has switched due to internal differences between the js parser and gqlparser
'Validate: Fields on correct type/Defined on implementors but not on interface':
- rule: 'Validate: Fields on correct type/Defined on implementors but not on interface'
errors:
- message: Cannot query field "nickname" on type "Pet". Did you mean to use an inline fragment on "Cat" or "Dog"?
locations:
- {line: 3, column: 9}

'Validate: Known directives/within schema language/with well placed directives':
- rule: 'Validate: Known directives/within schema language/with well placed directives'
skip: true
'Validate: Known directives/within schema language/with misplaced directives':

- rule: 'Validate: Known directives/within schema language/with misplaced directives'
skip: true
'Validate: Known type names/ignores type definitions':

- rule: 'Validate: Known type names/ignores type definitions'
skip: true
'Validate: Overlapping fields can be merged/return types must be unambiguous/reports correctly when a non-exclusive follows an exclusive':

- rule: 'Validate: Overlapping fields can be merged/return types must be unambiguous/reports correctly when a non-exclusive follows an exclusive'
skip: true
'Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing subfields':

- rule: 'Validate: Overlapping fields can be merged/return types must be unambiguous/disallows differing subfields'
skip: true

- rule: 'Validate: Values of correct type/.*custom scalar.*'
skip: "Custom scalars are a runtime feature, maybe they dont belong in here?"

- rule: 'Validate: Values of correct type/.*Variable default values.*'
skip: "TODO"
15 changes: 12 additions & 3 deletions validator/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,24 @@ type ErrorOption func(err *errors.Validation)

func Message(msg string, args ...interface{}) ErrorOption {
return func(err *errors.Validation) {
err.Message = fmt.Sprintf(msg, args...)
err.Message += fmt.Sprintf(msg, args...)
}
}

func SuggestList(typed string, suggestions []string) ErrorOption {
func SuggestListQuoted(prefix string, typed string, suggestions []string) ErrorOption {
suggested := suggestionList(typed, suggestions)
return func(err *errors.Validation) {
if len(suggested) > 0 {
err.Message += " Did you mean " + quotedOrList(suggested...) + "?"
err.Message += " " + prefix + " " + quotedOrList(suggested...) + "?"
}
}
}

func SuggestListUnquoted(prefix string, typed string, suggestions []string) ErrorOption {
suggested := suggestionList(typed, suggestions)
return func(err *errors.Validation) {
if len(suggested) > 0 {
err.Message += " " + prefix + " " + orList(suggested...) + "?"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions validator/known_argument_names.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func init() {

addError(
Message(`Unknown argument "%s" on field "%s" of type "%s".`, arg.Name, field.Name, parentDef.Name),
SuggestList(arg.Name, suggestions),
SuggestListQuoted("Did you mean", arg.Name, suggestions),
)
}
})
Expand All @@ -46,7 +46,7 @@ func init() {

addError(
Message(`Unknown argument "%s" on directive "@%s".`, arg.Name, directive.Name),
SuggestList(arg.Name, suggestions),
SuggestListQuoted("Did you mean", arg.Name, suggestions),
)
}
})
Expand Down
2 changes: 1 addition & 1 deletion validator/known_type_names.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func init() {
possibleTypes = append(possibleTypes, t.Name)
}

list := SuggestList(typeName, possibleTypes)
list := SuggestListQuoted("Did you mean", typeName, possibleTypes)

addError(
Message(`Unknown type "%s".`, typeName),
Expand Down
2 changes: 1 addition & 1 deletion validator/unique_input_field_names.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

func init() {
addRule("UniqueInputFieldNames", func(observers *Events, addError addErrFunc) {
observers.OnValue(func(walker *Walker, value gqlparser.Value) {
observers.OnValue(func(walker *Walker, fieldType gqlparser.Type, def *gqlparser.Definition, value gqlparser.Value) {
object, isObject := value.(gqlparser.ObjectValue)
if !isObject {
return
Expand Down
42 changes: 32 additions & 10 deletions validator/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package validator
import (
"fmt"
"io/ioutil"
"regexp"
"sort"
"strings"
"testing"

Expand All @@ -19,15 +21,25 @@ type Spec struct {
Schema int
Query string
Errors []errors.Validation
Skip bool
}

type Deviation struct {
Rule string
Errors []errors.Validation
Skip string

pattern *regexp.Regexp
}

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

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

var schemas []*gqlparser.Schema
for _, schema := range rawSchemas {
Expand All @@ -54,7 +66,6 @@ func TestSpec(t *testing.T) {
"OverlappingFieldsCanBeMerged",
"PossibleFragmentSpreads",
"ProvidedRequiredArguments",
"ValuesOfCorrectType",
"VariablesAreInputTypes",
"VariablesInAllowedPosition",
}
Expand Down Expand Up @@ -88,7 +99,7 @@ file:
}
}

func runSpec(schemas []*gqlparser.Schema, deviations map[string]*Spec, filename string) func(t *testing.T) {
func runSpec(schemas []*gqlparser.Schema, deviations []*Deviation, filename string) func(t *testing.T) {
var specs []Spec
readYaml(filename, &specs)
return func(t *testing.T) {
Expand All @@ -97,12 +108,14 @@ func runSpec(schemas []*gqlparser.Schema, deviations map[string]*Spec, filename
spec.Errors = nil
}
t.Run(spec.Name, func(t *testing.T) {
if deviation := deviations[spec.Name]; deviation != nil {
if deviation.Errors != nil {
spec.Errors = deviation.Errors
}
if deviation.Skip {
t.SkipNow()
for _, deviation := range deviations {
if deviation.pattern.MatchString(spec.Name) {
if deviation.Skip != "" {
t.Skip(deviation.Skip)
}
if deviation.Errors != nil {
spec.Errors = deviation.Errors
}
}
}

Expand All @@ -123,7 +136,16 @@ func runSpec(schemas []*gqlparser.Schema, deviations map[string]*Spec, filename
for i := range spec.Errors {
spec.Errors[i].Locations = nil
spec.Errors[i].Rule = spec.Rule

// remove inconsistent use of ;
spec.Errors[i].Message = strings.Replace(spec.Errors[i].Message, "; Did you mean", ". Did you mean", -1)
}
sort.Slice(spec.Errors, func(i, j int) bool {
return strings.Compare(spec.Errors[i].Message, spec.Errors[j].Message) > 0
})
sort.Slice(finalErrors, func(i, j int) bool {
return strings.Compare(finalErrors[i].Message, finalErrors[j].Message) > 0
})
assert.Equal(t, spec.Errors, finalErrors)

if t.Failed() {
Expand Down
Loading