Skip to content

Commit 690358b

Browse files
committed
field validation
1 parent 0356653 commit 690358b

3 files changed

Lines changed: 162 additions & 14 deletions

File tree

src/main/java/graphql/schema/GraphQLUnionType.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,18 @@ public Builder typeResolver(TypeResolver typeResolver) {
7070

7171

7272
public Builder possibleType(GraphQLType type) {
73+
assertNotNull(type, "possible type can't be null");
7374
types.add(type);
7475
return this;
7576
}
7677

78+
public Builder possibleTypes(GraphQLType... type) {
79+
for (GraphQLType graphQLType : type) {
80+
possibleType(graphQLType);
81+
}
82+
return this;
83+
}
84+
7785
public GraphQLUnionType build() {
7886
return new GraphQLUnionType(name, description, types, typeResolver);
7987
}

src/main/java/graphql/validation/rules/OverlappingFieldsCanBeMerged.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,88 @@ private Conflict findConflict(String responseName, FieldAndType fieldAndType1, F
5757
Field field1 = fieldAndType1.field;
5858
Field field2 = fieldAndType2.field;
5959

60+
GraphQLType type1 = fieldAndType1.graphQLType;
61+
GraphQLType type2 = fieldAndType2.graphQLType;
62+
6063
String fieldName1 = field1.getName();
6164
String fieldName2 = field2.getName();
6265
if (!fieldName1.equals(fieldName2)) {
63-
String reason = String.format("%s and %s are different fields", fieldName1, fieldName2);
66+
String reason = String.format("%s: %s and %s are different fields", responseName, fieldName1, fieldName2);
67+
return new Conflict(responseName, reason, field1, field2);
68+
}
69+
70+
if (!sameType(type1, type2)) {
71+
String reason = String.format("%s: they return differing types %s and %s", responseName, type1.getName(), type2.getName());
6472
return new Conflict(responseName, reason, field1, field2);
6573
}
74+
75+
// var arguments1 = ast1.arguments || [];
76+
// var arguments2 = ast2.arguments || [];
77+
// if (!sameArguments(arguments1, arguments2)) {
78+
// return [
79+
// [responseName, 'they have differing arguments'],
80+
// [ast1, ast2]
81+
// ];
82+
// }
83+
//
84+
// var directives1 = ast1.directives || [];
85+
// var directives2 = ast2.directives || [];
86+
// if (!sameDirectives(directives1, directives2)) {
87+
// return [
88+
// [responseName, 'they have differing directives'],
89+
// [ast1, ast2]
90+
// ];
91+
// }
92+
//
93+
// var selectionSet1 = ast1.selectionSet;
94+
// var selectionSet2 = ast2.selectionSet;
95+
// if (selectionSet1 && selectionSet2) {
96+
// var visitedFragmentNames = {};
97+
// var subfieldMap = collectFieldASTsAndDefs(
98+
// context,
99+
// type1,
100+
// selectionSet1,
101+
// visitedFragmentNames
102+
// );
103+
// subfieldMap = collectFieldASTsAndDefs(
104+
// context,
105+
// type2,
106+
// selectionSet2,
107+
// visitedFragmentNames,
108+
// subfieldMap
109+
// );
110+
// var conflicts = findConflicts(subfieldMap);
111+
// if (conflicts.length > 0) {
112+
// return [
113+
// [responseName, conflicts.map(([reason]) => reason)],
114+
// conflicts.reduce(
115+
// (list: Array<Field>, [, blameNodes]) => list.concat(blameNodes),
116+
// [ast1, ast2]
117+
// )
118+
// ];
119+
// }
120+
// }
121+
122+
66123
return null;
67124

68125
}
69126

127+
private boolean sameType(GraphQLType type1, GraphQLType type2) {
128+
if (type1 == null && type2 == null) return true;
129+
if (type1 == null) return false;
130+
if (type2 == null) return false;
131+
return type1.equals(type2);
132+
}
133+
134+
private boolean sameArguments() {
135+
return false;
136+
}
137+
138+
private boolean sameDirectives(List<Directive> directives1, List<Directive> directives2) {
139+
return false;
140+
}
141+
70142

71143
private void collectFields(Map<String, List<FieldAndType>> fieldMap, SelectionSet selectionSet, GraphQLOutputType parentType, Set<String> visitedFragmentSpreads) {
72144

src/test/groovy/graphql/validation/rules/OverlappingFieldsCanBeMergedTest.groovy

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
11
package graphql.validation.rules
22

3-
import graphql.Scalars
43
import graphql.language.Document
54
import graphql.language.SourceLocation
65
import graphql.parser.Parser
7-
import graphql.schema.GraphQLFieldDefinition
6+
import graphql.schema.GraphQLNonNull
87
import graphql.schema.GraphQLObjectType
98
import graphql.schema.GraphQLSchema
9+
import graphql.schema.TypeResolver
1010
import graphql.validation.LanguageTraversal
1111
import graphql.validation.RulesVisitor
1212
import graphql.validation.ValidationContext
1313
import graphql.validation.ValidationErrorCollector
1414
import spock.lang.Specification
1515

16+
import static graphql.Scalars.GraphQLInt
17+
import static graphql.Scalars.GraphQLString
18+
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition
19+
import static graphql.schema.GraphQLObjectType.newObject
20+
import static graphql.schema.GraphQLUnionType.newUnionType
21+
1622
class OverlappingFieldsCanBeMergedTest extends Specification {
1723

1824
ValidationErrorCollector errorCollector = new ValidationErrorCollector()
1925

2026

21-
def traverse(String query) {
22-
def objectType = GraphQLObjectType.newObject()
23-
.name("Test")
24-
.field(GraphQLFieldDefinition.newFieldDefinition().name("name").type(Scalars.GraphQLString).build())
25-
.field(GraphQLFieldDefinition.newFieldDefinition().name("nickname").type(Scalars.GraphQLString).build())
26-
.build();
27-
def schema = GraphQLSchema.newSchema().query(objectType).build()
27+
def traverse(String query, GraphQLSchema schema) {
28+
if (schema == null) {
29+
def objectType = newObject()
30+
.name("Test")
31+
.field(newFieldDefinition().name("name").type(GraphQLString).build())
32+
.field(newFieldDefinition().name("nickname").type(GraphQLString).build())
33+
.build();
34+
schema = GraphQLSchema.newSchema().query(objectType).build()
35+
}
2836

2937
Document document = new Parser().parseDocument(query)
3038
ValidationContext validationContext = new ValidationContext(schema, document)
@@ -43,7 +51,7 @@ class OverlappingFieldsCanBeMergedTest extends Specification {
4351
}
4452
"""
4553
when:
46-
traverse(query)
54+
traverse(query, null)
4755

4856
then:
4957
errorCollector.errors.isEmpty()
@@ -58,13 +66,73 @@ class OverlappingFieldsCanBeMergedTest extends Specification {
5866
}
5967
"""
6068
when:
61-
traverse(query)
69+
traverse(query, null)
6270

6371
then:
6472
errorCollector.getErrors().size() == 1
65-
errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: name and nickname are different fields"
66-
errorCollector.getErrors()[0].locations == [new SourceLocation(3, 17),new SourceLocation(4, 17)]
73+
errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: myName: name and nickname are different fields"
74+
errorCollector.getErrors()[0].locations == [new SourceLocation(3, 17), new SourceLocation(4, 17)]
75+
}
76+
77+
def 'conflicting scalar return types'() {
78+
79+
def StringBox = newObject().name("StringBox")
80+
.field(newFieldDefinition().name("scalar").type(GraphQLString).build())
81+
.build()
82+
def IntBox = newObject().name("IntBox")
83+
.field(newFieldDefinition().name("scalar").type(GraphQLInt).build())
84+
.build()
85+
86+
def NonNullStringBox1 = newObject().name("NonNullStringBox1")
87+
.field(newFieldDefinition().name("scalar").type(new GraphQLNonNull(GraphQLString)).build())
88+
.build()
89+
90+
def NonNullStringBox2 = newObject().name("NonNullStringBox2")
91+
.field(newFieldDefinition().name("scalar").type(new GraphQLNonNull(GraphQLString)).build())
92+
.build()
93+
94+
def BoxUnion = newUnionType()
95+
.name("BoxUnion")
96+
.possibleTypes(StringBox, IntBox, NonNullStringBox1, NonNullStringBox2)
97+
.typeResolver(new TypeResolver() {
98+
@Override
99+
GraphQLObjectType getType(Object object) {
100+
return null
101+
}
102+
})
103+
.build()
104+
def QueryRoot = newObject()
105+
.name("QueryRoot")
106+
.field(newFieldDefinition().name("boxUnion").type(BoxUnion).build()).build()
107+
def schema = GraphQLSchema.newSchema().query(QueryRoot).build()
67108

109+
given:
110+
def query = """
111+
{
112+
boxUnion {
113+
...on IntBox {
114+
scalar
115+
}
116+
...on StringBox {
117+
scalar
118+
}
119+
}
120+
}
121+
"""
122+
123+
when:
124+
traverse(query, schema)
125+
126+
then:
127+
errorCollector.getErrors().size() == 1
128+
errorCollector.getErrors()[0].message == "Validation error of type FieldsConflict: scalar: they return differing types Int and String"
129+
// [
130+
// { message: fieldsConflictMessage(
131+
// 'scalar',
132+
// 'they return differing types Int and String'
133+
// ),
134+
// locations: [ { line: 5, column: 15 }, { line: 8, column: 15 } ] }
135+
// ]);
68136
}
69137

70138
}

0 commit comments

Comments
 (0)