Skip to content

Commit 5d4d531

Browse files
committed
Merge pull request #184 from graphql/unique-input-fields
[RFC] Move input field uniqueness validator
2 parents 657fbbb + 9046c14 commit 5d4d531

File tree

5 files changed

+120
-23
lines changed

5 files changed

+120
-23
lines changed

src/language/__tests__/parser.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,6 @@ fragment MissingOn Type
107107
).to.throw('Syntax Error GraphQL (1:37) Unexpected $');
108108
});
109109

110-
it('duplicate keys in input object is syntax error', () => {
111-
expect(
112-
() => parse('{ field(arg: { a: 1, a: 2 }) }')
113-
).to.throw('Syntax Error GraphQL (1:22) Duplicate input object field a.');
114-
});
115-
116110
it('does not accept fragments named "on"', () => {
117111
expect(
118112
() => parse('fragment on on on { on }')

src/language/parser.js

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -528,10 +528,9 @@ function parseList(parser, isConst: boolean): ListValue {
528528
function parseObject(parser, isConst: boolean): ObjectValue {
529529
var start = parser.token.start;
530530
expect(parser, TokenKind.BRACE_L);
531-
var fieldNames = {};
532531
var fields = [];
533532
while (!skip(parser, TokenKind.BRACE_R)) {
534-
fields.push(parseObjectField(parser, isConst, fieldNames));
533+
fields.push(parseObjectField(parser, isConst));
535534
}
536535
return {
537536
kind: OBJECT,
@@ -543,24 +542,11 @@ function parseObject(parser, isConst: boolean): ObjectValue {
543542
/**
544543
* ObjectField[Const] : Name : Value[?Const]
545544
*/
546-
function parseObjectField(
547-
parser,
548-
isConst: boolean,
549-
fieldNames: {[name: string]: boolean}
550-
): ObjectField {
545+
function parseObjectField(parser, isConst: boolean): ObjectField {
551546
var start = parser.token.start;
552-
var name = parseName(parser);
553-
if (fieldNames.hasOwnProperty(name.value)) {
554-
throw syntaxError(
555-
parser.source,
556-
start,
557-
`Duplicate input object field ${name.value}.`
558-
);
559-
}
560-
fieldNames[name.value] = true;
561547
return {
562548
kind: OBJECT_FIELD,
563-
name,
549+
name: parseName(parser),
564550
value:
565551
(expect(parser, TokenKind.COLON), parseValueLiteral(parser, isConst)),
566552
loc: loc(parser, start)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
import { describe, it } from 'mocha';
11+
import { expectPassesRule, expectFailsRule } from './harness';
12+
import {
13+
UniqueInputFieldNames,
14+
duplicateInputFieldMessage,
15+
} from '../rules/UniqueInputFieldNames';
16+
17+
18+
function duplicateField(name, l1, c1, l2, c2) {
19+
return {
20+
message: duplicateInputFieldMessage(name),
21+
locations: [ { line: l1, column: c1 }, { line: l2, column: c2 } ],
22+
};
23+
}
24+
25+
describe('Validate: Unique input field names', () => {
26+
27+
it('input object with fields', () => {
28+
expectPassesRule(UniqueInputFieldNames, `
29+
{
30+
field(arg: { f: true })
31+
}
32+
`);
33+
});
34+
35+
it('same input object within two args', () => {
36+
expectPassesRule(UniqueInputFieldNames, `
37+
{
38+
field(arg1: { f: true }, arg2: { f: true })
39+
}
40+
`);
41+
});
42+
43+
it('multiple input object fields', () => {
44+
expectPassesRule(UniqueInputFieldNames, `
45+
{
46+
field(arg: { f1: "value", f2: "value", f3: "value" })
47+
}
48+
`);
49+
});
50+
51+
it('duplicate input object fields', () => {
52+
expectFailsRule(UniqueInputFieldNames, `
53+
{
54+
field(arg: { f1: "value", f1: "value" })
55+
}
56+
`, [
57+
duplicateField('f1', 3, 22, 3, 35)
58+
]);
59+
});
60+
61+
it('many duplicate input object fields', () => {
62+
expectFailsRule(UniqueInputFieldNames, `
63+
{
64+
field(arg: { f1: "value", f1: "value", f1: "value" })
65+
}
66+
`, [
67+
duplicateField('f1', 3, 22, 3, 35),
68+
duplicateField('f1', 3, 22, 3, 48)
69+
]);
70+
});
71+
72+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* @flow */
2+
/**
3+
* Copyright (c) 2015, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the BSD-style license found in the
7+
* LICENSE file in the root directory of this source tree. An additional grant
8+
* of patent rights can be found in the PATENTS file in the same directory.
9+
*/
10+
11+
import { GraphQLError } from '../../error';
12+
13+
14+
export function duplicateInputFieldMessage(fieldName: any): string {
15+
return `There can be only one input field named "${fieldName}".`;
16+
}
17+
18+
/**
19+
* Unique input field names
20+
*
21+
* A GraphQL input object value is only valid if all supplied fields are
22+
* uniquely named.
23+
*/
24+
export function UniqueInputFieldNames(): any {
25+
var knownNames = Object.create(null);
26+
return {
27+
ObjectValue() {
28+
knownNames = Object.create(null);
29+
},
30+
ObjectField(node) {
31+
var fieldName = node.name.value;
32+
if (knownNames[fieldName]) {
33+
return new GraphQLError(
34+
duplicateInputFieldMessage(fieldName),
35+
[ knownNames[fieldName], node.name ]
36+
);
37+
}
38+
knownNames[fieldName] = node.name;
39+
}
40+
};
41+
}

src/validation/specifiedRules.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ import {
7676
OverlappingFieldsCanBeMerged
7777
} from './rules/OverlappingFieldsCanBeMerged';
7878

79+
// Spec Section: "Input Object Field Uniqueness"
80+
import { UniqueInputFieldNames } from './rules/UniqueInputFieldNames';
81+
7982

8083
/**
8184
* This set includes all validation rules defined by the GraphQL spec.
@@ -103,4 +106,5 @@ export var specifiedRules = [
103106
DefaultValuesOfCorrectType,
104107
VariablesInAllowedPosition,
105108
OverlappingFieldsCanBeMerged,
109+
UniqueInputFieldNames,
106110
];

0 commit comments

Comments
 (0)