Skip to content

Commit 8bf2565

Browse files
authored
This allows circular types to be applied to. It fixes a StackOverflow bug (#34)
1 parent d70dbe6 commit 8bf2565

File tree

4 files changed

+127
-7
lines changed

4 files changed

+127
-7
lines changed

src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public boolean appliesTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsCo
6565
@Override
6666
public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) {
6767

68-
boolean suitable = DirectivesAndTypeWalker.isSuitable(argument, (inputType, directive) -> {
68+
boolean suitable = new DirectivesAndTypeWalker().isSuitable(argument, (inputType, directive) -> {
6969
boolean hasNamedDirective = directive.getName().equals(this.getName());
7070
if (hasNamedDirective) {
7171
inputType = Util.unwrapNonNull(inputType);

src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java

+22-6
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,45 @@
88
import graphql.schema.GraphQLInputObjectType;
99
import graphql.schema.GraphQLInputType;
1010
import graphql.schema.GraphQLList;
11+
import graphql.schema.GraphQLTypeUtil;
1112

13+
import java.util.HashMap;
1214
import java.util.List;
15+
import java.util.Map;
1316
import java.util.function.BiFunction;
1417

1518
@Internal
1619
public class DirectivesAndTypeWalker {
1720

18-
public static boolean isSuitable(GraphQLArgument argument, BiFunction<GraphQLInputType, GraphQLDirective, Boolean> isSuitable) {
21+
private final Map<String, Boolean> seenTypes = new HashMap<>();
22+
23+
public boolean isSuitable(GraphQLArgument argument, BiFunction<GraphQLInputType, GraphQLDirective, Boolean> isSuitable) {
1924
GraphQLInputType inputType = argument.getType();
2025
List<GraphQLDirective> directives = argument.getDirectives();
2126
return walkInputType(inputType, directives, isSuitable);
2227
}
2328

24-
private static boolean walkInputType(GraphQLInputType inputType, List<GraphQLDirective> directives, BiFunction<GraphQLInputType, GraphQLDirective, Boolean> isSuitable) {
29+
private boolean walkInputType(GraphQLInputType inputType, List<GraphQLDirective> directives, BiFunction<GraphQLInputType, GraphQLDirective, Boolean> isSuitable) {
30+
String typeName = GraphQLTypeUtil.unwrapAll(inputType).getName();
2531
GraphQLInputType unwrappedInputType = Util.unwrapNonNull(inputType);
2632
for (GraphQLDirective directive : directives) {
2733
if (isSuitable.apply(unwrappedInputType, directive)) {
28-
return true;
34+
return seen(typeName,true);
2935
}
3036
}
3137
if (unwrappedInputType instanceof GraphQLInputObjectType) {
3238
GraphQLInputObjectType inputObjType = (GraphQLInputObjectType) unwrappedInputType;
39+
if (seenTypes.containsKey(typeName)) {
40+
return seenTypes.get(typeName);
41+
}
42+
seen(typeName,false);
43+
3344
for (GraphQLInputObjectField inputField : inputObjType.getFieldDefinitions()) {
3445
inputType = inputField.getType();
3546
directives = inputField.getDirectives();
3647

3748
if (walkInputType(inputType, directives, isSuitable)) {
38-
return true;
49+
return seen(typeName,true);
3950
}
4051
}
4152
}
@@ -44,11 +55,16 @@ private static boolean walkInputType(GraphQLInputType inputType, List<GraphQLDir
4455
if (innerListType instanceof GraphQLDirectiveContainer) {
4556
directives = ((GraphQLDirectiveContainer) innerListType).getDirectives();
4657
if (walkInputType(innerListType, directives, isSuitable)) {
47-
return true;
58+
return seen(typeName,true);
4859
}
4960
}
5061
}
51-
return false;
62+
return seen(typeName,false);
63+
}
64+
65+
private boolean seen(String typeName, boolean flag) {
66+
seenTypes.put(typeName, flag);
67+
return flag;
5268
}
5369

5470
}

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

+72
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package graphql.validation.rules
33
import graphql.GraphQL
44
import graphql.execution.DataFetcherResult
55
import graphql.schema.DataFetcher
6+
import graphql.schema.DataFetchingEnvironment
67
import graphql.schema.idl.RuntimeWiring
78
import graphql.validation.TestUtil
89
import graphql.validation.constraints.DirectiveConstraints
10+
import graphql.validation.schemawiring.ValidationSchemaWiring
911
import spock.lang.Specification
1012

1113
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring
@@ -78,4 +80,74 @@ class ValidationRulesTest extends Specification {
7880
er.errors[2].message == "/cars/filter/model size must be between 0 and 10"
7981
er.errors[2].path == ["cars"]
8082
}
83+
84+
def "issue 17 - type references handled"() {
85+
86+
def directiveRules = DirectiveConstraints.newDirectiveConstraints().build()
87+
88+
def sdl = '''
89+
90+
''' + directiveRules.directivesSDL + '''
91+
92+
input NameRequest {
93+
# The title associated to the name
94+
title: String @Size(min : 1, max : 1)
95+
# The given name
96+
givenName: String! @Size(min : 1, max : 1)
97+
# Middle Name
98+
middleName: String
99+
# Last Name
100+
surName: String!
101+
102+
# recursion
103+
inner : NameRequest
104+
105+
innerList : [NameRequest!]
106+
}
107+
108+
type Query {
109+
request( arg : NameRequest!) : String
110+
}
111+
'''
112+
113+
ValidationRules validationRules = ValidationRules.newValidationRules()
114+
.onValidationErrorStrategy(OnValidationErrorStrategy.RETURN_NULL).build()
115+
def validationWiring = new ValidationSchemaWiring(validationRules)
116+
117+
DataFetcher df = { DataFetchingEnvironment env ->
118+
return "OK"
119+
}
120+
121+
def runtime = RuntimeWiring.newRuntimeWiring()
122+
.type(newTypeWiring("Query").dataFetcher("request", df))
123+
.directiveWiring(validationWiring)
124+
.build()
125+
def graphQLSchema = TestUtil.schema(sdl, runtime)
126+
def graphQL = GraphQL.newGraphQL(graphQLSchema).build()
127+
128+
when:
129+
130+
def er = graphQL.execute('''
131+
{
132+
request (
133+
arg : {
134+
title : "Mr BRAD", givenName : "BRADLEY" , surName : "BAKER"
135+
inner : { title : "Mr BRAD", givenName : "BRADLEY" , surName : "BAKER" }
136+
innerList : [ { title : "Mr BRAD", givenName : "BRADLEY" , surName : "BAKER" } ]
137+
}
138+
)
139+
}
140+
''')
141+
then:
142+
er != null
143+
er.data["request"] == null
144+
er.errors.size() == 6
145+
146+
er.errors[0].getMessage() == "/request/arg/givenName size must be between 1 and 1"
147+
er.errors[1].getMessage() == "/request/arg/inner/givenName size must be between 1 and 1"
148+
er.errors[2].getMessage() == "/request/arg/inner/title size must be between 1 and 1"
149+
er.errors[3].getMessage() == "/request/arg/innerList[0]/givenName size must be between 1 and 1"
150+
er.errors[4].getMessage() == "/request/arg/innerList[0]/title size must be between 1 and 1"
151+
er.errors[5].getMessage() == "/request/arg/title size must be between 1 and 1"
152+
}
81153
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package graphql.validation.util
2+
3+
4+
import graphql.schema.GraphQLObjectType
5+
import graphql.validation.TestUtil
6+
import spock.lang.Specification
7+
8+
class DirectivesAndTypeWalkerTest extends Specification {
9+
10+
def "can walk self referencing types"() {
11+
def sdl = """
12+
input TestInput @deprecated {
13+
name : String
14+
list : [TestInput]
15+
self : TestInput
16+
}
17+
18+
type Query {
19+
f(arg : TestInput) : String
20+
}
21+
"""
22+
23+
def schema = TestUtil.schema(sdl)
24+
def arg = (schema.getType("Query") as GraphQLObjectType).getFieldDefinition("f").getArgument("arg")
25+
def callback = { t, d -> true }
26+
when:
27+
def suitable = new DirectivesAndTypeWalker().isSuitable(arg, callback)
28+
29+
then:
30+
suitable
31+
}
32+
}

0 commit comments

Comments
 (0)