Skip to content

Commit a119e6f

Browse files
committed
Commit:
9046c14d135e7e0785b6a43cd0e0ceef7e8773b4 [9046c14] Parents: 657fbbba26 Author: Lee Byron <[email protected]> Date: 24 September 2015 at 7:49:45 AM SGT [RFC] Move input field uniqueness validator This proposes moving input field uniqueness assertion from the parser to the validator. This simplifies the parser and allows these errors to be reported as part of the collection of validation errors which is actually more valuable. A follow-up RFC against the spec will be added
1 parent 6861a04 commit a119e6f

File tree

4 files changed

+125
-22
lines changed

4 files changed

+125
-22
lines changed

language/parser/parser.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -706,18 +706,16 @@ func parseObject(parser *Parser, isConst bool) (*ast.ObjectValue, error) {
706706
return nil, err
707707
}
708708
fields := []*ast.ObjectField{}
709-
fieldNames := map[string]bool{}
710709
for {
711710
if skp, err := skip(parser, lexer.TokenKind[lexer.BRACE_R]); err != nil {
712711
return nil, err
713712
} else if skp {
714713
break
715714
}
716-
field, fieldName, err := parseObjectField(parser, isConst, fieldNames)
715+
field, err := parseObjectField(parser, isConst)
717716
if err != nil {
718717
return nil, err
719718
}
720-
fieldNames[fieldName] = true
721719
fields = append(fields, field)
722720
}
723721
return ast.NewObjectValue(&ast.ObjectValue{
@@ -729,30 +727,25 @@ func parseObject(parser *Parser, isConst bool) (*ast.ObjectValue, error) {
729727
/**
730728
* ObjectField[Const] : Name : Value[?Const]
731729
*/
732-
func parseObjectField(parser *Parser, isConst bool, fieldNames map[string]bool) (*ast.ObjectField, string, error) {
730+
func parseObjectField(parser *Parser, isConst bool) (*ast.ObjectField, error) {
733731
start := parser.Token.Start
734732
name, err := parseName(parser)
735733
if err != nil {
736-
return nil, "", err
737-
}
738-
fieldName := name.Value
739-
if _, ok := fieldNames[fieldName]; ok {
740-
descp := fmt.Sprintf("Duplicate input object field %v.", fieldName)
741-
return nil, "", gqlerrors.NewSyntaxError(parser.Source, start, descp)
734+
return nil, err
742735
}
743736
_, err = expect(parser, lexer.TokenKind[lexer.COLON])
744737
if err != nil {
745-
return nil, "", err
738+
return nil, err
746739
}
747740
value, err := parseValueLiteral(parser, isConst)
748741
if err != nil {
749-
return nil, "", err
742+
return nil, err
750743
}
751744
return ast.NewObjectField(&ast.ObjectField{
752745
Name: name,
753746
Value: value,
754747
Loc: loc(parser, start),
755-
}), fieldName, nil
748+
}), nil
756749
}
757750

758751
/* Implements the parsing rules in the Directives section. */

language/parser/parser_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,6 @@ func TestParsesConstantDefaultValues(t *testing.T) {
161161
testErrorMessage(t, test)
162162
}
163163

164-
func TestDuplicatedKeysInInputObject(t *testing.T) {
165-
test := errorMessageTest{
166-
`{ field(arg: { a: 1, a: 2 }) }'`,
167-
`Syntax Error GraphQL (1:22) Duplicate input object field a.`,
168-
false,
169-
}
170-
testErrorMessage(t, test)
171-
}
172-
173164
func TestDoesNotAcceptFragmentsNameOn(t *testing.T) {
174165
test := errorMessageTest{
175166
`fragment on on on { on }`,

rules.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var SpecifiedRules = []ValidationRuleFn{
3434
ScalarLeafsRule,
3535
UniqueArgumentNamesRule,
3636
UniqueFragmentNamesRule,
37+
UniqueInputFieldNamesRule,
3738
UniqueOperationNamesRule,
3839
VariablesAreInputTypesRule,
3940
VariablesInAllowedPositionRule,
@@ -1660,6 +1661,59 @@ func UniqueFragmentNamesRule(context *ValidationContext) *ValidationRuleInstance
16601661
}
16611662
}
16621663

1664+
/**
1665+
* UniqueInputFieldNamesRule
1666+
*
1667+
* A GraphQL input object value is only valid if all supplied fields are
1668+
* uniquely named.
1669+
*/
1670+
func UniqueInputFieldNamesRule(context *ValidationContext) *ValidationRuleInstance {
1671+
knownNameStack := []map[string]*ast.Name{}
1672+
knownNames := map[string]*ast.Name{}
1673+
1674+
visitorOpts := &visitor.VisitorOptions{
1675+
KindFuncMap: map[string]visitor.NamedVisitFuncs{
1676+
kinds.ObjectValue: visitor.NamedVisitFuncs{
1677+
Enter: func(p visitor.VisitFuncParams) (string, interface{}) {
1678+
knownNameStack = append(knownNameStack, knownNames)
1679+
knownNames = map[string]*ast.Name{}
1680+
return visitor.ActionNoChange, nil
1681+
},
1682+
Leave: func(p visitor.VisitFuncParams) (string, interface{}) {
1683+
// pop
1684+
knownNames, knownNameStack = knownNameStack[len(knownNameStack)-1], knownNameStack[:len(knownNameStack)-1]
1685+
return visitor.ActionNoChange, nil
1686+
},
1687+
},
1688+
kinds.ObjectField: visitor.NamedVisitFuncs{
1689+
Kind: func(p visitor.VisitFuncParams) (string, interface{}) {
1690+
var action = visitor.ActionNoChange
1691+
var result interface{}
1692+
if node, ok := p.Node.(*ast.ObjectField); ok {
1693+
fieldName := ""
1694+
if node.Name != nil {
1695+
fieldName = node.Name.Value
1696+
}
1697+
if knownNameAST, ok := knownNames[fieldName]; ok {
1698+
return newValidationRuleError(
1699+
fmt.Sprintf(`There can be only one input field named "%v".`, fieldName),
1700+
[]ast.Node{knownNameAST, node.Name},
1701+
)
1702+
} else {
1703+
knownNames[fieldName] = node.Name
1704+
}
1705+
1706+
}
1707+
return action, result
1708+
},
1709+
},
1710+
},
1711+
}
1712+
return &ValidationRuleInstance{
1713+
VisitorOpts: visitorOpts,
1714+
}
1715+
}
1716+
16631717
/**
16641718
* UniqueOperationNamesRule
16651719
* Unique operation names
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package graphql_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/graphql-go/graphql"
7+
"github.com/graphql-go/graphql/gqlerrors"
8+
"github.com/graphql-go/graphql/testutil"
9+
)
10+
11+
func TestValidate_UniqueInputFieldNames_InputObjectWithFields(t *testing.T) {
12+
testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, `
13+
{
14+
field(arg: { f: true })
15+
}
16+
`)
17+
}
18+
func TestValidate_UniqueInputFieldNames_SameInputObjectWithinTwoArgs(t *testing.T) {
19+
testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, `
20+
{
21+
field(arg1: { f: true }, arg2: { f: true })
22+
}
23+
`)
24+
}
25+
func TestValidate_UniqueInputFieldNames_MultipleInputObjectFields(t *testing.T) {
26+
testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, `
27+
{
28+
field(arg: { f1: "value", f2: "value", f3: "value" })
29+
}
30+
`)
31+
}
32+
func TestValidate_UniqueInputFieldNames_AllowsForNestedInputObjectsWithSimilarFields(t *testing.T) {
33+
testutil.ExpectPassesRule(t, graphql.UniqueInputFieldNamesRule, `
34+
{
35+
field(arg: {
36+
deep: {
37+
deep: {
38+
id: 1
39+
}
40+
id: 1
41+
}
42+
id: 1
43+
})
44+
}
45+
`)
46+
}
47+
func TestValidate_UniqueInputFieldNames_DuplicateInputObjectFields(t *testing.T) {
48+
testutil.ExpectFailsRule(t, graphql.UniqueInputFieldNamesRule, `
49+
{
50+
field(arg: { f1: "value", f1: "value" })
51+
}
52+
`, []gqlerrors.FormattedError{
53+
testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 35),
54+
})
55+
}
56+
func TestValidate_UniqueInputFieldNames_ManyDuplicateInputObjectFields(t *testing.T) {
57+
testutil.ExpectFailsRule(t, graphql.UniqueInputFieldNamesRule, `
58+
{
59+
field(arg: { f1: "value", f1: "value", f1: "value" })
60+
}
61+
`, []gqlerrors.FormattedError{
62+
testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 35),
63+
testutil.RuleError(`There can be only one input field named "f1".`, 3, 22, 3, 48),
64+
})
65+
}

0 commit comments

Comments
 (0)