Skip to content

Commit

Permalink
validation: UniqueDirectivesPerLocation
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Mar 22, 2017
1 parent dcf7e59 commit e45f26d
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 41 deletions.
17 changes: 14 additions & 3 deletions internal/common/directive.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ type Directive struct {
Args ArgumentList
}

func ParseDirectives(l *lexer.Lexer) map[string]*Directive {
directives := make(map[string]*Directive)
func ParseDirectives(l *lexer.Lexer) DirectiveList {
var directives DirectiveList
for l.Peek() == '@' {
l.ConsumeToken('@')
d := &Directive{}
Expand All @@ -19,7 +19,18 @@ func ParseDirectives(l *lexer.Lexer) map[string]*Directive {
if l.Peek() == '(' {
d.Args = ParseArguments(l)
}
directives[d.Name.Name] = d
directives = append(directives, d)
}
return directives
}

type DirectiveList []*Directive

func (l DirectiveList) Get(name string) *Directive {
for _, d := range l {
if d.Name.Name == name {
return d
}
}
return nil
}
6 changes: 3 additions & 3 deletions internal/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,8 @@ type typeAssertExec struct {
typeExec iExec
}

func skipByDirective(r *request, directives map[string]*common.Directive) bool {
if d, ok := directives["skip"]; ok {
func skipByDirective(r *request, directives common.DirectiveList) bool {
if d := directives.Get("skip"); d != nil {
p := valuePacker{valueType: reflect.TypeOf(false)}
v, err := p.pack(r, r.resolveVar(d.Args.MustGet("if").Value))
if err != nil {
Expand All @@ -682,7 +682,7 @@ func skipByDirective(r *request, directives map[string]*common.Directive) bool {
}
}

if d, ok := directives["include"]; ok {
if d := directives.Get("include"); d != nil {
p := valuePacker{valueType: reflect.TypeOf(false)}
v, err := p.pack(r, r.resolveVar(d.Args.MustGet("if").Value))
if err != nil {
Expand Down
10 changes: 5 additions & 5 deletions internal/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type Operation struct {
Name lexer.Ident
Vars common.InputValueList
SelSet *SelectionSet
Directives map[string]*common.Directive
Directives common.DirectiveList
}

type OperationType string
Expand All @@ -61,7 +61,7 @@ type Fragment struct {
type FragmentDecl struct {
Fragment
Name lexer.Ident
Directives map[string]*common.Directive
Directives common.DirectiveList
}

type SelectionSet struct {
Expand All @@ -77,18 +77,18 @@ type Field struct {
Alias lexer.Ident
Name lexer.Ident
Arguments common.ArgumentList
Directives map[string]*common.Directive
Directives common.DirectiveList
SelSet *SelectionSet
}

type InlineFragment struct {
Fragment
Directives map[string]*common.Directive
Directives common.DirectiveList
}

type FragmentSpread struct {
Name lexer.Ident
Directives map[string]*common.Directive
Directives common.DirectiveList
}

func (Field) isSelection() {}
Expand Down
15 changes: 8 additions & 7 deletions internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type Enum struct {

type EnumValue struct {
Name string
Directives map[string]*common.Directive
Directives common.DirectiveList
Desc string
}

Expand Down Expand Up @@ -136,7 +136,7 @@ type Field struct {
Name string
Args common.InputValueList
Type common.Type
Directives map[string]*common.Directive
Directives common.DirectiveList
Desc string
}

Expand Down Expand Up @@ -271,15 +271,16 @@ func resolveField(s *Schema, f *Field) error {
return resolveInputObject(s, f.Args)
}

func resolveDirectives(s *Schema, directives map[string]*common.Directive) error {
for name, d := range directives {
dd, ok := s.Directives[name]
func resolveDirectives(s *Schema, directives common.DirectiveList) error {
for _, d := range directives {
dirName := d.Name.Name
dd, ok := s.Directives[dirName]
if !ok {
return errors.Errorf("directive %q not found", name)
return errors.Errorf("directive %q not found", dirName)
}
for _, arg := range d.Args {
if dd.Args.Get(arg.Name.Name) == nil {
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, name)
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
}
}
for _, arg := range dd.Args {
Expand Down
2 changes: 1 addition & 1 deletion internal/tests/testdata/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ require('./src/validation/__tests__/LoneAnonymousOperation-test');
require('./src/validation/__tests__/ProvidedNonNullArguments-test');
require('./src/validation/__tests__/ScalarLeafs-test');
require('./src/validation/__tests__/UniqueArgumentNames-test');
// require('./src/validation/__tests__/UniqueDirectivesPerLocation-test');
require('./src/validation/__tests__/UniqueDirectivesPerLocation-test');
require('./src/validation/__tests__/UniqueFragmentNames-test');
// require('./src/validation/__tests__/UniqueInputFieldNames-test');
require('./src/validation/__tests__/UniqueOperationNames-test');
Expand Down
149 changes: 149 additions & 0 deletions internal/tests/testdata/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,155 @@
}
]
},
{
"name": "Validate: Directives Are Unique Per Location/no directives",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Directives Are Unique Per Location/unique directives in different locations",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type @directiveA {\n field @directiveB\n }\n ",
"errors": []
},
{
"name": "Validate: Directives Are Unique Per Location/unique directives in same locations",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type @directiveA @directiveB {\n field @directiveA @directiveB\n }\n ",
"errors": []
},
{
"name": "Validate: Directives Are Unique Per Location/same directives in different locations",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type @directiveA {\n field @directiveA\n }\n ",
"errors": []
},
{
"name": "Validate: Directives Are Unique Per Location/same directives in similar locations",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type {\n field @directive\n field @directive\n }\n ",
"errors": []
},
{
"name": "Validate: Directives Are Unique Per Location/duplicate directives in one location",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type {\n field @directive @directive\n }\n ",
"errors": [
{
"message": "The directive \"directive\" can only be used once at this location.",
"locations": [
{
"line": 3,
"column": 15
},
{
"line": 3,
"column": 26
}
]
}
]
},
{
"name": "Validate: Directives Are Unique Per Location/many duplicate directives in one location",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type {\n field @directive @directive @directive\n }\n ",
"errors": [
{
"message": "The directive \"directive\" can only be used once at this location.",
"locations": [
{
"line": 3,
"column": 15
},
{
"line": 3,
"column": 26
}
]
},
{
"message": "The directive \"directive\" can only be used once at this location.",
"locations": [
{
"line": 3,
"column": 15
},
{
"line": 3,
"column": 37
}
]
}
]
},
{
"name": "Validate: Directives Are Unique Per Location/different duplicate directives in one location",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type {\n field @directiveA @directiveB @directiveA @directiveB\n }\n ",
"errors": [
{
"message": "The directive \"directiveA\" can only be used once at this location.",
"locations": [
{
"line": 3,
"column": 15
},
{
"line": 3,
"column": 39
}
]
},
{
"message": "The directive \"directiveB\" can only be used once at this location.",
"locations": [
{
"line": 3,
"column": 27
},
{
"line": 3,
"column": 51
}
]
}
]
},
{
"name": "Validate: Directives Are Unique Per Location/duplicate directives in many locations",
"rule": "UniqueDirectivesPerLocation",
"query": "\n fragment Test on Type @directive @directive {\n field @directive @directive\n }\n ",
"errors": [
{
"message": "The directive \"directive\" can only be used once at this location.",
"locations": [
{
"line": 2,
"column": 29
},
{
"line": 2,
"column": 40
}
]
},
{
"message": "The directive \"directive\" can only be used once at this location.",
"locations": [
{
"line": 3,
"column": 15
},
{
"line": 3,
"column": 26
}
]
}
]
},
{
"name": "Validate: Unique fragment names/no fragments",
"rule": "UniqueFragmentNames",
Expand Down
36 changes: 24 additions & 12 deletions internal/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func Validate(s *schema.Schema, doc *query.Document) []*errors.QueryError {
c.validateDirectives("FRAGMENT_DEFINITION", frag.Directives)
t := c.resolveType(&frag.On)
// continue even if t is nil
if !canBeFragment(t) {
if t != nil && !canBeFragment(t) {
c.addErr(frag.On.Loc, "FragmentsOnCompositeTypes", "Fragment %q cannot condition on non composite type %q.", frag.Name.Name, t)
continue
}
Expand Down Expand Up @@ -202,16 +202,22 @@ func (c *context) resolveType(t common.Type) common.Type {
return t2
}

func (c *context) validateDirectives(loc string, directives map[string]*common.Directive) {
for name, d := range directives {
names := make(nameSet)
func (c *context) validateDirectives(loc string, directives common.DirectiveList) {
directiveNames := make(nameSet)
for _, d := range directives {
dirName := d.Name.Name
c.validateNameCustomMsg(directiveNames, d.Name, "UniqueDirectivesPerLocation", func() string {
return fmt.Sprintf("The directive %q can only be used once at this location.", dirName)
})

argNames := make(nameSet)
for _, arg := range d.Args {
c.validateName(names, arg.Name, "UniqueArgumentNames", "argument")
c.validateName(argNames, arg.Name, "UniqueArgumentNames", "argument")
}

dd, ok := c.schema.Directives[name]
dd, ok := c.schema.Directives[dirName]
if !ok {
c.addErr(d.Name.Loc, "KnownDirectives", "Unknown directive %q.", name)
c.addErr(d.Name.Loc, "KnownDirectives", "Unknown directive %q.", dirName)
continue
}

Expand All @@ -223,23 +229,29 @@ func (c *context) validateDirectives(loc string, directives map[string]*common.D
}
}
if !locOK {
c.addErr(d.Name.Loc, "KnownDirectives", "Directive %q may not be used on %s.", name, loc)
c.addErr(d.Name.Loc, "KnownDirectives", "Directive %q may not be used on %s.", dirName, loc)
}

c.validateArguments(d.Args, dd.Args, d.Name.Loc,
func() string { return fmt.Sprintf("directive %q", "@"+d.Name.Name) },
func() string { return fmt.Sprintf("Directive %q", "@"+d.Name.Name) },
func() string { return fmt.Sprintf("directive %q", "@"+dirName) },
func() string { return fmt.Sprintf("Directive %q", "@"+dirName) },
)
}
return
}

type nameSet map[string]errors.Location

func (c *context) validateName(set nameSet, name lexer.Ident, rule, kind string) {
func (c *context) validateName(set nameSet, name lexer.Ident, rule string, kind string) {
c.validateNameCustomMsg(set, name, rule, func() string {
return fmt.Sprintf("There can be only one %s named %q.", kind, name.Name)
})
}

func (c *context) validateNameCustomMsg(set nameSet, name lexer.Ident, rule string, msg func() string) {
if loc, ok := set[name.Name]; ok {
c.errs = append(c.errs, &errors.QueryError{
Message: fmt.Sprintf("There can be only one %s named %q.", kind, name.Name),
Message: msg(),
Locations: []errors.Location{loc, name.Loc},
Rule: rule,
})
Expand Down
Loading

0 comments on commit e45f26d

Please sign in to comment.