diff --git a/src/execution/__tests__/oneof-test.ts b/src/execution/__tests__/oneof-test.ts index 85153c244a..d738832e2e 100644 --- a/src/execution/__tests__/oneof-test.ts +++ b/src/execution/__tests__/oneof-test.ts @@ -259,6 +259,32 @@ describe('Execute: Handles OneOf Input Objects', () => { }); }); + it('errors with missing variable as an additional field', () => { + const query = ` + query ($a: String, $b: Int) { + test(input: { a: $a, b: $b }) { + a + b + } + } + `; + const result = executeQuery(query, rootValue, { a: 'abc' }); + + expectJSON(result).toDeepEqual({ + data: { + test: null, + }, + errors: [ + { + message: + 'Argument "Query.test(input:)" has invalid value: Expected variable "$b" provided to field "b" for OneOf Input Object type "TestInputObject" to provide a runtime value.', + locations: [{ line: 3, column: 23 }], + path: ['test'], + }, + ], + }); + }); + it('errors with nulled fragment variable for field', () => { const query = ` query { diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index cf799ef0c2..5f46b066d1 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -677,6 +677,26 @@ describe('coerceInputLiteral', () => { ); }); + it('rejects multiple oneOf fields when one variable is unprovided', () => { + testWithVariables( + '($a: String, $b: String)', + { a: 'abc' }, + '{ a: $a, b: $b }', + testOneOfInputObj, + undefined, + ); + }); + + it('rejects oneOf field when variable is null', () => { + testWithVariables( + '($a: String)', + { a: null }, + '{ a: $a }', + testOneOfInputObj, + undefined, + ); + }); + it('preserves explicit null variables in input object fields', () => { testWithVariables( '($foo: Boolean)', diff --git a/src/utilities/__tests__/validateInputValue-test.ts b/src/utilities/__tests__/validateInputValue-test.ts index 82c6814b56..31cdd57490 100644 --- a/src/utilities/__tests__/validateInputValue-test.ts +++ b/src/utilities/__tests__/validateInputValue-test.ts @@ -1111,6 +1111,27 @@ describe('validateInputLiteral', () => { ], ); }); + + it('errors with multiple variables when one is missing', () => { + testWithVariables( + '($foo: Int, $bar: Int)', + { foo: 123 }, + '{ foo: $foo, bar: $bar }', + TestInputObject, + [ + { + error: + 'Expected variable "$bar" provided to field "bar" for OneOf Input Object type "TestInputObject" to provide a runtime value.', + path: [], + }, + { + error: + 'Within OneOf Input Object type "TestInputObject", exactly one field must be specified, and the value for that field must be non-null.', + path: [], + }, + ], + ); + }); }); describe('for GraphQLList', () => { diff --git a/src/utilities/__tests__/valueFromAST-test.ts b/src/utilities/__tests__/valueFromAST-test.ts index bf152e93e3..a30c9b9337 100644 --- a/src/utilities/__tests__/valueFromAST-test.ts +++ b/src/utilities/__tests__/valueFromAST-test.ts @@ -303,4 +303,10 @@ describe('valueFromAST', () => { requiredBool: true, }); }); + + it('rejects multiple oneOf fields when one variable is unprovided', () => { + expectValueFrom('{ a: $a, b: $b }', testOneOfInputObj, { + a: 'abc', + }).to.equal(undefined); + }); }); diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index a1836d786c..1ba8a18af0 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -258,13 +258,18 @@ export function coerceInputLiteral( } if (type.isOneOf) { - const keys = Object.keys(coercedValue); - if (keys.length !== 1) { + const coercedKeys = Object.keys(coercedValue); + if (fieldNodes.size !== 1 || coercedKeys.length !== 1) { return; // Invalid: not exactly one key, intentionally return no value. } - if (coercedValue[keys[0]] === null) { - return; // Invalid: value not non-null, intentionally return no value. + for (const [fieldName, fieldNode] of fieldNodes) { + if ( + fieldNode.value.kind === Kind.NULL || + coercedValue[fieldName] === null + ) { + return; // Invalid: value not non-null, intentionally return no value. + } } } diff --git a/src/utilities/valueFromAST.ts b/src/utilities/valueFromAST.ts index ee803ac865..84534ca73b 100644 --- a/src/utilities/valueFromAST.ts +++ b/src/utilities/valueFromAST.ts @@ -133,13 +133,18 @@ export function valueFromAST( } if (type.isOneOf) { - const keys = Object.keys(coercedObj); - if (keys.length !== 1) { + const coercedKeys = Object.keys(coercedObj); + if (fieldNodes.size !== 1 || coercedKeys.length !== 1) { return; // Invalid: not exactly one key, intentionally return no value. } - if (coercedObj[keys[0]] === null) { - return; // Invalid: value not non-null, intentionally return no value. + for (const [fieldName, fieldNode] of fieldNodes) { + if ( + fieldNode.value.kind === Kind.NULL || + coercedObj[fieldName] === null + ) { + return; // Invalid: value not non-null, intentionally return no value. + } } }