Skip to content

Commit

Permalink
validation: UniqueOperationNames
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Mar 22, 2017
1 parent a5a1160 commit eeaa510
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 14 deletions.
19 changes: 11 additions & 8 deletions internal/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type OperationList []*Operation

func (l OperationList) Get(name string) *Operation {
for _, f := range l {
if f.Name == name {
if f.Name.Name == name {
return f
}
}
Expand All @@ -28,18 +28,18 @@ func (l OperationList) Get(name string) *Operation {

type Operation struct {
Type OperationType
Name string
Name lexer.Ident
Vars common.InputValueList
SelSet *SelectionSet
Directives map[string]*common.Directive
Loc errors.Location
}

type OperationType string

const (
Query OperationType = "QUERY"
Mutation = "MUTATION"
Query OperationType = "QUERY"
Mutation = "MUTATION"
Subscription = "SUBSCRIPTION"
)

type Fragment struct {
Expand Down Expand Up @@ -109,7 +109,7 @@ func parseDocument(l *lexer.Lexer) *Document {
for l.Peek() != scanner.EOF {
if l.Peek() == '{' {
op := &Operation{Type: Query}
op.Loc = l.Location()
op.Name.Loc = l.Location()
op.SelSet = parseSelectionSet(l)
d.Operations = append(d.Operations, op)
continue
Expand All @@ -122,6 +122,9 @@ func parseDocument(l *lexer.Lexer) *Document {
case "mutation":
d.Operations = append(d.Operations, parseOperation(l, Mutation))

case "subscription":
d.Operations = append(d.Operations, parseOperation(l, Subscription))

case "fragment":
f := parseFragment(l)
d.Fragments[f.Name] = f
Expand All @@ -135,9 +138,9 @@ func parseDocument(l *lexer.Lexer) *Document {

func parseOperation(l *lexer.Lexer, opType OperationType) *Operation {
op := &Operation{Type: opType}
op.Loc = l.Location()
op.Name.Loc = l.Location()
if l.Peek() == scanner.Ident {
op.Name = l.ConsumeIdent()
op.Name = l.ConsumeIdentWithLoc()
}
op.Directives = common.ParseDirectives(l)
if l.Peek() == '(' {
Expand Down
4 changes: 2 additions & 2 deletions internal/tests/testdata/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ let fakeModules = {
it(name, f) {
switch (name) {
case 'ignores type definitions':
case 'anon operation with a subscription':
return;
}
names.push(name);
Expand Down Expand Up @@ -76,12 +75,13 @@ require('./src/validation/__tests__/UniqueArgumentNames-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');
require('./src/validation/__tests__/UniqueOperationNames-test');
require('./src/validation/__tests__/UniqueVariableNames-test');
require('./src/validation/__tests__/VariablesAreInputTypes-test');
// require('./src/validation/__tests__/VariablesInAllowedPosition-test');

let output = JSON.stringify(tests, null, 2)
output = output.replace(/There can only be one/g, 'There can be only one');
output = output.replace('{stringListField: [\\"one\\", 2], requiredField: true}', '{requiredField: true, stringListField: [\\"one\\", 2]}');
output = output.replace('{requiredField: null, intField: null}', '{intField: null, requiredField: null}');
output = output.replace(' Did you mean to use an inline fragment on \\"Dog\\" or \\"Cat\\"?', '');
Expand Down
112 changes: 112 additions & 0 deletions internal/tests/testdata/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,22 @@
}
]
},
{
"name": "Validate: Anonymous operation must be alone/anon operation with a subscription",
"rule": "LoneAnonymousOperation",
"query": "\n {\n fieldA\n }\n subscription Foo {\n fieldB\n }\n ",
"errors": [
{
"message": "This anonymous operation must be the only defined operation.",
"locations": [
{
"line": 2,
"column": 7
}
]
}
]
},
{
"name": "Validate: Provided required arguments/ignores unknown arguments",
"rule": "ProvidedNonNullArguments",
Expand Down Expand Up @@ -2065,6 +2081,102 @@
}
]
},
{
"name": "Validate: Unique operation names/no operations",
"rule": "UniqueOperationNames",
"query": "\n fragment fragA on Type {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique operation names/one anon operation",
"rule": "UniqueOperationNames",
"query": "\n {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique operation names/one named operation",
"rule": "UniqueOperationNames",
"query": "\n query Foo {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique operation names/multiple operations",
"rule": "UniqueOperationNames",
"query": "\n query Foo {\n field\n }\n\n query Bar {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique operation names/multiple operations of different types",
"rule": "UniqueOperationNames",
"query": "\n query Foo {\n field\n }\n\n mutation Bar {\n field\n }\n\n subscription Baz {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique operation names/fragment and operation named the same",
"rule": "UniqueOperationNames",
"query": "\n query Foo {\n ...Foo\n }\n fragment Foo on Type {\n field\n }\n ",
"errors": []
},
{
"name": "Validate: Unique operation names/multiple operations of same name",
"rule": "UniqueOperationNames",
"query": "\n query Foo {\n fieldA\n }\n query Foo {\n fieldB\n }\n ",
"errors": [
{
"message": "There can be only one operation named \"Foo\".",
"locations": [
{
"line": 2,
"column": 13
},
{
"line": 5,
"column": 13
}
]
}
]
},
{
"name": "Validate: Unique operation names/multiple ops of same name of different types (mutation)",
"rule": "UniqueOperationNames",
"query": "\n query Foo {\n fieldA\n }\n mutation Foo {\n fieldB\n }\n ",
"errors": [
{
"message": "There can be only one operation named \"Foo\".",
"locations": [
{
"line": 2,
"column": 13
},
{
"line": 5,
"column": 16
}
]
}
]
},
{
"name": "Validate: Unique operation names/multiple ops of same name of different types (subscription)",
"rule": "UniqueOperationNames",
"query": "\n query Foo {\n fieldA\n }\n subscription Foo {\n fieldB\n }\n ",
"errors": [
{
"message": "There can be only one operation named \"Foo\".",
"locations": [
{
"line": 2,
"column": 13
},
{
"line": 5,
"column": 20
}
]
}
]
},
{
"name": "Validate: Unique variable names/unique variable names",
"rule": "UniqueVariableNames",
Expand Down
14 changes: 10 additions & 4 deletions internal/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,20 @@ func Validate(s *schema.Schema, doc *query.Document) []*errors.QueryError {
doc: doc,
}

opNames := make(nameSet)
for _, op := range doc.Operations {
if op.Name == "" && len(doc.Operations) != 1 {
c.addErr(op.Loc, "LoneAnonymousOperation", "This anonymous operation must be the only defined operation.")
if op.Name.Name == "" && len(doc.Operations) != 1 {
c.addErr(op.Name.Loc, "LoneAnonymousOperation", "This anonymous operation must be the only defined operation.")
}
if op.Name.Name != "" {
c.validateName(opNames, op.Name, "UniqueOperationNames", "operation")
}

c.validateDirectives(string(op.Type), op.Directives)

names := make(nameSet)
varNames := make(nameSet)
for _, v := range op.Vars {
c.validateName(names, v.Name, "UniqueVariableNames", "variable")
c.validateName(varNames, v.Name, "UniqueVariableNames", "variable")

t := c.resolveType(v.Type)
if !canBeInput(t) {
Expand All @@ -68,6 +72,8 @@ func Validate(s *schema.Schema, doc *query.Document) []*errors.QueryError {
entryPoint = s.EntryPoints["query"]
case query.Mutation:
entryPoint = s.EntryPoints["mutation"]
case query.Subscription:
entryPoint = s.EntryPoints["subscription"]
default:
panic("unreachable")
}
Expand Down

0 comments on commit eeaa510

Please sign in to comment.