diff --git a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java index 288b318..f1ccf6a 100644 --- a/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java +++ b/src/main/java/graphql/validation/constraints/AbstractDirectiveConstraint.java @@ -64,7 +64,7 @@ public boolean appliesTo(GraphQLFieldDefinition fieldDefinition, GraphQLFieldsCo @Override public boolean appliesTo(GraphQLArgument argument, GraphQLFieldDefinition fieldDefinition, GraphQLFieldsContainer fieldsContainer) { - boolean suitable = DirectivesAndTypeWalker.isSuitable(argument, (inputType, directive) -> { + boolean suitable = new DirectivesAndTypeWalker().isSuitable(argument, (inputType, directive) -> { boolean hasNamedDirective = directive.getName().equals(this.getName()); if (hasNamedDirective) { inputType = Util.unwrapNonNull(inputType); diff --git a/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java b/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java index 6d32590..53d7e07 100644 --- a/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java +++ b/src/main/java/graphql/validation/util/DirectivesAndTypeWalker.java @@ -8,34 +8,45 @@ import graphql.schema.GraphQLInputObjectType; import graphql.schema.GraphQLInputType; import graphql.schema.GraphQLList; +import graphql.schema.GraphQLTypeUtil; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.BiFunction; @Internal public class DirectivesAndTypeWalker { - public static boolean isSuitable(GraphQLArgument argument, BiFunction isSuitable) { + private final Map seenTypes = new HashMap<>(); + + public boolean isSuitable(GraphQLArgument argument, BiFunction isSuitable) { GraphQLInputType inputType = argument.getType(); List directives = argument.getDirectives(); return walkInputType(inputType, directives, isSuitable); } - private static boolean walkInputType(GraphQLInputType inputType, List directives, BiFunction isSuitable) { + private boolean walkInputType(GraphQLInputType inputType, List directives, BiFunction isSuitable) { + String typeName = GraphQLTypeUtil.unwrapAll(inputType).getName(); GraphQLInputType unwrappedInputType = Util.unwrapNonNull(inputType); for (GraphQLDirective directive : directives) { if (isSuitable.apply(unwrappedInputType, directive)) { - return true; + return seen(typeName,true); } } if (unwrappedInputType instanceof GraphQLInputObjectType) { GraphQLInputObjectType inputObjType = (GraphQLInputObjectType) unwrappedInputType; + if (seenTypes.containsKey(typeName)) { + return seenTypes.get(typeName); + } + seen(typeName,false); + for (GraphQLInputObjectField inputField : inputObjType.getFieldDefinitions()) { inputType = inputField.getType(); directives = inputField.getDirectives(); if (walkInputType(inputType, directives, isSuitable)) { - return true; + return seen(typeName,true); } } } @@ -44,11 +55,16 @@ private static boolean walkInputType(GraphQLInputType inputType, List + return "OK" + } + + def runtime = RuntimeWiring.newRuntimeWiring() + .type(newTypeWiring("Query").dataFetcher("request", df)) + .directiveWiring(validationWiring) + .build() + def graphQLSchema = TestUtil.schema(sdl, runtime) + def graphQL = GraphQL.newGraphQL(graphQLSchema).build() + + when: + + def er = graphQL.execute(''' + { + request ( + arg : { + title : "Mr BRAD", givenName : "BRADLEY" , surName : "BAKER" + inner : { title : "Mr BRAD", givenName : "BRADLEY" , surName : "BAKER" } + innerList : [ { title : "Mr BRAD", givenName : "BRADLEY" , surName : "BAKER" } ] + } + ) + } + ''') + then: + er != null + er.data["request"] == null + er.errors.size() == 6 + + er.errors[0].getMessage() == "/request/arg/givenName size must be between 1 and 1" + er.errors[1].getMessage() == "/request/arg/inner/givenName size must be between 1 and 1" + er.errors[2].getMessage() == "/request/arg/inner/title size must be between 1 and 1" + er.errors[3].getMessage() == "/request/arg/innerList[0]/givenName size must be between 1 and 1" + er.errors[4].getMessage() == "/request/arg/innerList[0]/title size must be between 1 and 1" + er.errors[5].getMessage() == "/request/arg/title size must be between 1 and 1" + } } diff --git a/src/test/groovy/graphql/validation/util/DirectivesAndTypeWalkerTest.groovy b/src/test/groovy/graphql/validation/util/DirectivesAndTypeWalkerTest.groovy new file mode 100644 index 0000000..30d157e --- /dev/null +++ b/src/test/groovy/graphql/validation/util/DirectivesAndTypeWalkerTest.groovy @@ -0,0 +1,32 @@ +package graphql.validation.util + + +import graphql.schema.GraphQLObjectType +import graphql.validation.TestUtil +import spock.lang.Specification + +class DirectivesAndTypeWalkerTest extends Specification { + + def "can walk self referencing types"() { + def sdl = """ + input TestInput @deprecated { + name : String + list : [TestInput] + self : TestInput + } + + type Query { + f(arg : TestInput) : String + } + """ + + def schema = TestUtil.schema(sdl) + def arg = (schema.getType("Query") as GraphQLObjectType).getFieldDefinition("f").getArgument("arg") + def callback = { t, d -> true } + when: + def suitable = new DirectivesAndTypeWalker().isSuitable(arg, callback) + + then: + suitable + } +}