Skip to content

Commit

Permalink
validation: UniqueFragmentNames
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Mar 22, 2017
1 parent eeaa510 commit dcf7e59
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 15 deletions.
2 changes: 1 addition & 1 deletion internal/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ func (e *objectExec) execSelectionSet(ctx context.Context, r *request, selSet *q
continue
}
execSel(func() {
e.execFragment(ctx, r, &r.doc.Fragments[spread.Name.Name].Fragment, resolver, addResult)
e.execFragment(ctx, r, &r.doc.Fragments.Get(spread.Name.Name).Fragment, resolver, addResult)
})

default:
Expand Down
30 changes: 19 additions & 11 deletions internal/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

type Document struct {
Operations OperationList
Fragments map[string]*NamedFragment
Fragments FragmentList
}

type OperationList []*Operation
Expand All @@ -26,6 +26,17 @@ func (l OperationList) Get(name string) *Operation {
return nil
}

type FragmentList []*FragmentDecl

func (l FragmentList) Get(name string) *FragmentDecl {
for _, f := range l {
if f.Name.Name == name {
return f
}
}
return nil
}

type Operation struct {
Type OperationType
Name lexer.Ident
Expand All @@ -47,9 +58,9 @@ type Fragment struct {
SelSet *SelectionSet
}

type NamedFragment struct {
type FragmentDecl struct {
Fragment
Name string
Name lexer.Ident
Directives map[string]*common.Directive
}

Expand Down Expand Up @@ -103,9 +114,7 @@ func Parse(queryString string) (*Document, *errors.QueryError) {
}

func parseDocument(l *lexer.Lexer) *Document {
d := &Document{
Fragments: make(map[string]*NamedFragment),
}
d := &Document{}
for l.Peek() != scanner.EOF {
if l.Peek() == '{' {
op := &Operation{Type: Query}
Expand All @@ -126,8 +135,7 @@ func parseDocument(l *lexer.Lexer) *Document {
d.Operations = append(d.Operations, parseOperation(l, Subscription))

case "fragment":
f := parseFragment(l)
d.Fragments[f.Name] = f
d.Fragments = append(d.Fragments, parseFragment(l))

default:
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "fragment"`, x))
Expand Down Expand Up @@ -155,9 +163,9 @@ func parseOperation(l *lexer.Lexer, opType OperationType) *Operation {
return op
}

func parseFragment(l *lexer.Lexer) *NamedFragment {
f := &NamedFragment{}
f.Name = l.ConsumeIdent()
func parseFragment(l *lexer.Lexer) *FragmentDecl {
f := &FragmentDecl{}
f.Name = l.ConsumeIdentWithLoc()
l.ConsumeKeyword("on")
f.On = common.TypeName{Ident: l.ConsumeIdentWithLoc()}
f.Directives = common.ParseDirectives(l)
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 @@ -73,7 +73,7 @@ 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__/UniqueFragmentNames-test');
require('./src/validation/__tests__/UniqueFragmentNames-test');
// require('./src/validation/__tests__/UniqueInputFieldNames-test');
require('./src/validation/__tests__/UniqueOperationNames-test');
require('./src/validation/__tests__/UniqueVariableNames-test');
Expand Down
70 changes: 70 additions & 0 deletions internal/tests/testdata/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,76 @@
}
]
},
{
"name": "Validate: Unique fragment names/no fragments",
"rule": "UniqueFragmentNames",
"query": "\n {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique fragment names/one fragment",
"rule": "UniqueFragmentNames",
"query": "\n {\n ...fragA\n }\n\n fragment fragA on Type {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique fragment names/many fragments",
"rule": "UniqueFragmentNames",
"query": "\n {\n ...fragA\n ...fragB\n ...fragC\n }\n fragment fragA on Type {\n fieldA\n }\n fragment fragB on Type {\n fieldB\n }\n fragment fragC on Type {\n fieldC\n }\n ",
"errors": []
},
{
"name": "Validate: Unique fragment names/inline fragments are always unique",
"rule": "UniqueFragmentNames",
"query": "\n {\n ...on Type {\n fieldA\n }\n ...on Type {\n fieldB\n }\n }\n ",
"errors": []
},
{
"name": "Validate: Unique fragment names/fragment and operation named the same",
"rule": "UniqueFragmentNames",
"query": "\n query Foo {\n ...Foo\n }\n fragment Foo on Type {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique fragment names/fragments named the same",
"rule": "UniqueFragmentNames",
"query": "\n {\n ...fragA\n }\n fragment fragA on Type {\n fieldA\n }\n fragment fragA on Type {\n fieldB\n }\n ",
"errors": [
{
"message": "There can be only one fragment named \"fragA\".",
"locations": [
{
"line": 5,
"column": 16
},
{
"line": 8,
"column": 16
}
]
}
]
},
{
"name": "Validate: Unique fragment names/fragments named the same without being referenced",
"rule": "UniqueFragmentNames",
"query": "\n fragment fragA on Type {\n fieldA\n }\n fragment fragA on Type {\n fieldB\n }\n ",
"errors": [
{
"message": "There can be only one fragment named \"fragA\".",
"locations": [
{
"line": 2,
"column": 16
},
{
"line": 5,
"column": 16
}
]
}
]
},
{
"name": "Validate: Unique operation names/no operations",
"rule": "UniqueOperationNames",
Expand Down
6 changes: 4 additions & 2 deletions internal/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ func Validate(s *schema.Schema, doc *query.Document) []*errors.QueryError {
c.validateSelectionSet(op.SelSet, entryPoint)
}

fragNames := make(nameSet)
for _, frag := range doc.Fragments {
c.validateName(fragNames, frag.Name, "UniqueFragmentNames", "fragment")
c.validateDirectives("FRAGMENT_DEFINITION", frag.Directives)
t := c.resolveType(&frag.On)
// continue even if t is nil
if !canBeFragment(t) {
c.addErr(frag.On.Loc, "FragmentsOnCompositeTypes", "Fragment %q cannot condition on non composite type %q.", frag.Name, t)
c.addErr(frag.On.Loc, "FragmentsOnCompositeTypes", "Fragment %q cannot condition on non composite type %q.", frag.Name.Name, t)
continue
}
c.validateSelectionSet(frag.SelSet, t)
Expand Down Expand Up @@ -160,7 +162,7 @@ func (c *context) validateSelection(sel query.Selection, t common.Type) {

case *query.FragmentSpread:
c.validateDirectives("FRAGMENT_SPREAD", sel.Directives)
if _, ok := c.doc.Fragments[sel.Name.Name]; !ok {
if frag := c.doc.Fragments.Get(sel.Name.Name); frag == nil {
c.addErr(sel.Name.Loc, "KnownFragmentNames", "Unknown fragment %q.", sel.Name.Name)
}

Expand Down

0 comments on commit dcf7e59

Please sign in to comment.