From 447740cb92124bfaa6e8cc42c24c9c718efab1fb Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 3 May 2026 17:33:51 +0300 Subject: [PATCH] fix(OneOf): reject invalid default-filled OneOf runtime inputs Coercion should fail if there are fewer than one keys pre-coercion. The scenario is a post-coercion single key secondary to a default. Defaults are invalid for OneOf, but this should still fail coercion. --- .../__tests__/coerceInputValue-test.ts | 19 +++++++++++++++++++ src/utilities/coerceInputValue.ts | 17 ++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index f8910bd5ba..22006dfc2c 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -190,6 +190,13 @@ describe('coerceInputValue', () => { }, isOneOf: true, }); + const TestInvalidOneOfInputObjectWithDefault = new GraphQLInputObjectType({ + name: 'TestInvalidOneOfInputObjectWithDefault', + fields: { + foo: { type: GraphQLInt, default: { value: 123 } }, + }, + isOneOf: true, + }); it('returns for valid input', () => { test({ foo: 123 }, TestInputObject, { foo: 123 }); @@ -203,6 +210,18 @@ describe('coerceInputValue', () => { test({ bar: null }, TestInputObject, undefined); }); + it('invalid if an omitted field would be filled by a default', () => { + test({}, TestInvalidOneOfInputObjectWithDefault, undefined); + }); + + it('invalid if an undefined field would be filled by a default', () => { + test( + { foo: undefined }, + TestInvalidOneOfInputObjectWithDefault, + undefined, + ); + }); + it('invalid for an invalid field', () => { test({ foo: NaN }, TestInputObject, undefined); }); diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index 1ba8a18af0..8937fd9a92 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -73,12 +73,15 @@ export function coerceInputValue( const coercedValue: ObjMap = Object.create(null); const fieldDefs = type.getFields(); - const hasUndefinedField = Object.keys(inputValue).some( - (name) => - inputValue[name] !== undefined && !Object.hasOwn(fieldDefs, name), - ); - if (hasUndefinedField) { - return; // Invalid: intentionally return no value. + let definedFieldCount = 0; + for (const fieldName of Object.keys(inputValue)) { + if (inputValue[fieldName] === undefined) { + continue; + } + definedFieldCount++; + if (!Object.hasOwn(fieldDefs, fieldName)) { + return; // Invalid: intentionally return no value. + } } for (const field of Object.values(fieldDefs)) { const fieldValue = inputValue[field.name]; @@ -101,7 +104,7 @@ export function coerceInputValue( if (type.isOneOf) { const keys = Object.keys(coercedValue); - if (keys.length !== 1) { + if (definedFieldCount !== 1 || keys.length !== 1) { return; // Invalid: intentionally return no value. }