-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Handle list of @oneOf input objects #3370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,14 @@ | |
| import graphql.Assert; | ||
| import graphql.Internal; | ||
| import graphql.i18n.I18n; | ||
| import graphql.language.ArrayValue; | ||
| import graphql.language.ObjectField; | ||
| import graphql.language.ObjectValue; | ||
| import graphql.language.Value; | ||
| import graphql.schema.GraphQLInputObjectField; | ||
| import graphql.schema.GraphQLInputObjectType; | ||
| import graphql.schema.GraphQLInputType; | ||
| import graphql.schema.GraphQLList; | ||
| import graphql.schema.GraphQLType; | ||
| import graphql.schema.GraphQLTypeUtil; | ||
|
|
||
|
|
@@ -17,18 +19,33 @@ | |
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static graphql.schema.GraphQLTypeUtil.isList; | ||
|
|
||
| @Internal | ||
| final class ValuesResolverOneOfValidation { | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| static void validateOneOfInputTypes(GraphQLType type, Object inputValue, Value<?> argumentValue, String argumentName, Locale locale) { | ||
| GraphQLType unwrappedType = GraphQLTypeUtil.unwrapNonNull(type); | ||
| GraphQLType unwrappedNonNullType = GraphQLTypeUtil.unwrapNonNull(type); | ||
|
|
||
| if (isList(unwrappedNonNullType) | ||
| && !ValuesResolverConversion.isNullValue(inputValue) | ||
| && inputValue instanceof List | ||
| && argumentValue instanceof ArrayValue) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Am I being too defensive here? |
||
| GraphQLType elementType = ((GraphQLList) unwrappedNonNullType).getWrappedType(); | ||
| List<Object> inputList = (List<Object>) inputValue; | ||
| List<Value> argumentList = ((ArrayValue) argumentValue).getValues(); | ||
|
|
||
| for (int i = 0; i < argumentList.size(); i++) { | ||
| validateOneOfInputTypes(elementType, inputList.get(i), argumentList.get(i), argumentName, locale); | ||
| } | ||
| } | ||
|
|
||
| if (unwrappedType instanceof GraphQLInputObjectType && !ValuesResolverConversion.isNullValue(inputValue)) { | ||
| if (unwrappedNonNullType instanceof GraphQLInputObjectType && !ValuesResolverConversion.isNullValue(inputValue)) { | ||
| Assert.assertTrue(inputValue instanceof Map, () -> String.format("The coerced argument %s GraphQLInputObjectType is unexpectedly not a map", argumentName)); | ||
| Map<String, Object> objectMap = (Map<String, Object>) inputValue; | ||
|
|
||
| GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) unwrappedType; | ||
| GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) unwrappedNonNullType; | ||
|
|
||
| if (inputObjectType.isOneOf()) { | ||
| validateOneOfInputTypesInternal(inputObjectType, argumentValue, objectMap, locale); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,6 @@ import graphql.language.Value | |
| import graphql.language.VariableDefinition | ||
| import graphql.language.VariableReference | ||
| import graphql.schema.CoercingParseValueException | ||
| import graphql.schema.GraphQLNonNull | ||
| import spock.lang.Specification | ||
| import spock.lang.Unroll | ||
|
|
||
|
|
@@ -373,7 +372,7 @@ class ValuesResolverTest extends Specification { | |
| e.message == "Exactly one key must be specified for OneOf type 'oneOfInputObject'." | ||
|
|
||
| when: "input type is wrapped in non-null" | ||
| def nonNullInputObjectType = GraphQLNonNull.nonNull(inputObjectType) | ||
| def nonNullInputObjectType = nonNull(inputObjectType) | ||
| def fieldArgumentNonNull = newArgument().name("arg").type(nonNullInputObjectType).build() | ||
| ValuesResolver.getArgumentValues([fieldArgumentNonNull], [argument], variables, graphQLContext, locale) | ||
|
|
||
|
|
@@ -603,6 +602,243 @@ class ValuesResolverTest extends Specification { | |
|
|
||
| } | ||
|
|
||
| def "getArgumentValues: invalid oneOf list input because element contains duplicate key - #testCase"() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is a remix of the original issue. Adds the variable case |
||
| given: "schema defining input object" | ||
| def inputObjectType = newInputObject() | ||
| .name("oneOfInputObject") | ||
| .withAppliedDirective(Directives.OneOfDirective.toAppliedDirective()) | ||
| .field(newInputObjectField() | ||
| .name("a") | ||
| .type(GraphQLString) | ||
| .build()) | ||
| .field(newInputObjectField() | ||
| .name("b") | ||
| .type(GraphQLInt) | ||
| .build()) | ||
| .build() | ||
|
|
||
| when: | ||
| def argument = new Argument("arg", inputArray) | ||
| def fieldArgumentList = newArgument().name("arg").type(list(inputObjectType)).build() | ||
| ValuesResolver.getArgumentValues([fieldArgumentList], [argument], variables, graphQLContext, locale) | ||
|
|
||
| then: | ||
| def e = thrown(OneOfTooManyKeysException) | ||
| e.message == "Exactly one key must be specified for OneOf type 'oneOfInputObject'." | ||
|
|
||
| where: | ||
|
|
||
| testCase | inputArray | variables | ||
|
|
||
| '[{ a: "abc", b: 123 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .value(buildObjectLiteral([ | ||
| a: StringValue.of("abc"), | ||
| b: IntValue.of(123) | ||
| ])).build() | ||
| | CoercedVariables.emptyVariables() | ||
|
|
||
| '[{ a: "abc" }, { a: "xyz", b: 789 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("xyz"), | ||
| b: IntValue.of(789) | ||
| ]), | ||
| ]).build() | ||
| | CoercedVariables.emptyVariables() | ||
|
|
||
| '[{ a: "abc" }, $var ] [{ a: "abc" }, { a: "xyz", b: 789 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| VariableReference.of("var") | ||
| ]).build() | ||
| | CoercedVariables.of("var": [a: "xyz", b: 789]) | ||
|
|
||
| } | ||
|
|
||
| def "getArgumentValues: invalid oneOf list input because element contains null value - #testCase"() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's check null values! |
||
| given: "schema defining input object" | ||
| def inputObjectType = newInputObject() | ||
| .name("oneOfInputObject") | ||
| .withAppliedDirective(Directives.OneOfDirective.toAppliedDirective()) | ||
| .field(newInputObjectField() | ||
| .name("a") | ||
| .type(GraphQLString) | ||
| .build()) | ||
| .field(newInputObjectField() | ||
| .name("b") | ||
| .type(GraphQLInt) | ||
| .build()) | ||
| .build() | ||
|
|
||
| when: | ||
| def argument = new Argument("arg", inputArray) | ||
| def fieldArgumentList = newArgument().name("arg").type(list(inputObjectType)).build() | ||
| ValuesResolver.getArgumentValues([fieldArgumentList], [argument], variables, graphQLContext, locale) | ||
|
|
||
| then: | ||
| def e = thrown(OneOfNullValueException) | ||
| e.message == "OneOf type field 'oneOfInputObject.a' must be non-null." | ||
|
|
||
| where: | ||
|
|
||
| testCase | inputArray | variables | ||
|
|
||
| '[{ a: "abc" }, { a: null }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| buildObjectLiteral([ | ||
| a: NullValue.of() | ||
| ]), | ||
| ]).build() | ||
| | CoercedVariables.emptyVariables() | ||
|
|
||
| '[{ a: "abc" }, { a: $var }] [{ a: "abc" }, { a: null }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| buildObjectLiteral([ | ||
| a: VariableReference.of("var") | ||
| ]), | ||
| ]).build() | ||
| | CoercedVariables.of("var": null) | ||
|
|
||
| } | ||
|
|
||
| def "getArgumentValues: invalid oneOf non-null list input because element contains duplicate key - #testCase"() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For fun let's try two combination cases: a non-null list and a list of non-null inputs |
||
| given: "schema defining input object" | ||
| def inputObjectType = newInputObject() | ||
| .name("oneOfInputObject") | ||
| .withAppliedDirective(Directives.OneOfDirective.toAppliedDirective()) | ||
| .field(newInputObjectField() | ||
| .name("a") | ||
| .type(GraphQLString) | ||
| .build()) | ||
| .field(newInputObjectField() | ||
| .name("b") | ||
| .type(GraphQLInt) | ||
| .build()) | ||
| .build() | ||
|
|
||
| when: | ||
| def argument = new Argument("arg", inputArray) | ||
| def fieldArgumentList = newArgument().name("arg").type(nonNull(list(inputObjectType))).build() | ||
| ValuesResolver.getArgumentValues([fieldArgumentList], [argument], variables, graphQLContext, locale) | ||
|
|
||
| then: | ||
| def e = thrown(OneOfTooManyKeysException) | ||
| e.message == "Exactly one key must be specified for OneOf type 'oneOfInputObject'." | ||
|
|
||
| where: | ||
|
|
||
| testCase | inputArray | variables | ||
|
|
||
| '[{ a: "abc", b: 123 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .value(buildObjectLiteral([ | ||
| a: StringValue.of("abc"), | ||
| b: IntValue.of(123) | ||
| ])).build() | ||
| | CoercedVariables.emptyVariables() | ||
|
|
||
| '[{ a: "abc" }, { a: "xyz", b: 789 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("xyz"), | ||
| b: IntValue.of(789) | ||
| ]), | ||
| ]).build() | ||
| | CoercedVariables.emptyVariables() | ||
|
|
||
| '[{ a: "abc" }, $var ] [{ a: "abc" }, { a: "xyz", b: 789 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| VariableReference.of("var") | ||
| ]).build() | ||
| | CoercedVariables.of("var": [a: "xyz", b: 789]) | ||
|
|
||
| } | ||
|
|
||
| def "getArgumentValues: invalid oneOf list input with non-nullable elements, because element contains duplicate key - #testCase"() { | ||
| given: "schema defining input object" | ||
| def inputObjectType = newInputObject() | ||
| .name("oneOfInputObject") | ||
| .withAppliedDirective(Directives.OneOfDirective.toAppliedDirective()) | ||
| .field(newInputObjectField() | ||
| .name("a") | ||
| .type(GraphQLString) | ||
| .build()) | ||
| .field(newInputObjectField() | ||
| .name("b") | ||
| .type(GraphQLInt) | ||
| .build()) | ||
| .build() | ||
|
|
||
| when: | ||
| def argument = new Argument("arg", inputArray) | ||
| def fieldArgumentList = newArgument().name("arg").type(list(nonNull(inputObjectType))).build() | ||
| ValuesResolver.getArgumentValues([fieldArgumentList], [argument], variables, graphQLContext, locale) | ||
|
|
||
| then: | ||
| def e = thrown(OneOfTooManyKeysException) | ||
| e.message == "Exactly one key must be specified for OneOf type 'oneOfInputObject'." | ||
|
|
||
| where: | ||
|
|
||
| testCase | inputArray | variables | ||
|
|
||
| '[{ a: "abc", b: 123 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .value(buildObjectLiteral([ | ||
| a: StringValue.of("abc"), | ||
| b: IntValue.of(123) | ||
| ])).build() | ||
| | CoercedVariables.emptyVariables() | ||
|
|
||
| '[{ a: "abc" }, { a: "xyz", b: 789 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("xyz"), | ||
| b: IntValue.of(789) | ||
| ]), | ||
| ]).build() | ||
| | CoercedVariables.emptyVariables() | ||
|
|
||
| '[{ a: "abc" }, $var ] [{ a: "abc" }, { a: "xyz", b: 789 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| VariableReference.of("var") | ||
| ]).build() | ||
| | CoercedVariables.of("var": [a: "xyz", b: 789]) | ||
|
|
||
| } | ||
|
|
||
| def "getArgumentValues: valid oneOf input - #testCase"() { | ||
| given: "schema defining input object" | ||
| def inputObjectType = newInputObject() | ||
|
|
@@ -651,6 +887,54 @@ class ValuesResolverTest extends Specification { | |
|
|
||
| } | ||
|
|
||
| def "getArgumentValues: valid oneOf list input - #testCase"() { | ||
| given: "schema defining input object" | ||
| def inputObjectType = newInputObject() | ||
| .name("oneOfInputObject") | ||
| .withAppliedDirective(Directives.OneOfDirective.toAppliedDirective()) | ||
| .field(newInputObjectField() | ||
| .name("a") | ||
| .type(GraphQLString) | ||
| .build()) | ||
| .field(newInputObjectField() | ||
| .name("b") | ||
| .type(GraphQLInt) | ||
| .build()) | ||
| .build() | ||
|
|
||
| when: | ||
| def argument = new Argument("arg", inputArray) | ||
| def fieldArgumentList = newArgument().name("arg").type(list(inputObjectType)).build() | ||
| def values = ValuesResolver.getArgumentValues([fieldArgumentList], [argument], variables, graphQLContext, locale) | ||
|
|
||
| then: | ||
| values == expectedValues | ||
|
|
||
| where: | ||
|
|
||
| testCase | inputArray | variables | expectedValues | ||
|
|
||
| '[{ a: "abc"}]' | ||
| | ArrayValue.newArrayValue() | ||
| .value(buildObjectLiteral([ | ||
| a: StringValue.of("abc"), | ||
| ])).build() | ||
| | CoercedVariables.emptyVariables() | ||
| | [arg: [[a: "abc"]]] | ||
|
|
||
| '[{ a: "abc" }, $var ] [{ a: "abc" }, { b: 789 }]' | ||
| | ArrayValue.newArrayValue() | ||
| .values([ | ||
| buildObjectLiteral([ | ||
| a: StringValue.of("abc") | ||
| ]), | ||
| VariableReference.of("var") | ||
| ]).build() | ||
| | CoercedVariables.of("var": [b: 789]) | ||
| | [arg: [[a: "abc"], [b: 789]]] | ||
|
|
||
| } | ||
|
|
||
| def "getArgumentValues: invalid oneOf input no values where passed - #testCase"() { | ||
| given: "schema defining input object" | ||
| def inputObjectType = newInputObject() | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename this variable as wrapping could mean either non-null or list, I want to be clear it's only taking off non-null wrapping
Naming is hard. Could also be
nonNullUnwrappedType🤔