From 531c6a2201337ac7828753f6271302ae60d5facd Mon Sep 17 00:00:00 2001 From: Cris Naranjo Date: Fri, 4 Apr 2025 17:39:21 +0200 Subject: [PATCH 1/5] feat(execution): add max coercion errors option to execution context --- src/execution/__tests__/executor-test.ts | 66 ++++++++++++++++++++++++ src/execution/execute.ts | 6 ++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index c758d3e426..1578e78f2a 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -11,6 +11,7 @@ import { Kind } from '../../language/kinds'; import { parse } from '../../language/parser'; import { + GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, @@ -1320,4 +1321,69 @@ describe('Execute: Handles basic execution tasks', () => { expect(result).to.deep.equal({ data: { foo: { bar: 'bar' } } }); expect(possibleTypes).to.deep.equal([fooObject]); }); + + it('uses a different number of max coercion errors', () => { + const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + dummy: { type: GraphQLString }, + }, + }), + mutation: new GraphQLObjectType({ + name: 'Mutation', + fields: { + updateUser: { + type: GraphQLString, + args: { + data: { + type: new GraphQLInputObjectType({ + name: 'User', + fields: { + email: { type: new GraphQLNonNull(GraphQLString) }, + }, + }), + }, + }, + }, + }, + }), + }); + + const document = parse(` + mutation ($data: User) { + updateUser(data: $data) + } + `); + + const executionOptions = { + maxCoercionErrors: 1, + }; + + const result = executeSync({ + schema, + document, + variableValues: { + data: { + email: '', + wrongArg: 'wrong', + wrongArg2: 'wrong', + wrongArg3: 'wrong', + }, + }, + executionOptions, + }); + + // Returns at least 2 errors, one for the first 'wrongArg', and one for coercion limit + expect(result.errors).to.have.lengthOf( + executionOptions.maxCoercionErrors + 1, + ); + expect( + result.errors && result.errors.length > 0 + ? result.errors[result.errors.length - 1].message + : undefined, + ).to.equal( + 'Too many errors processing variables, error limit reached. Execution aborted.', + ); + }); }); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 55c22ea9de..268008f9ed 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -152,6 +152,9 @@ export interface ExecutionArgs { fieldResolver?: Maybe>; typeResolver?: Maybe>; subscribeFieldResolver?: Maybe>; + executionOptions?: { + maxCoercionErrors?: number; + }; } /** @@ -286,6 +289,7 @@ export function buildExecutionContext( fieldResolver, typeResolver, subscribeFieldResolver, + executionOptions, } = args; let operation: OperationDefinitionNode | undefined; @@ -329,7 +333,7 @@ export function buildExecutionContext( schema, variableDefinitions, rawVariableValues ?? {}, - { maxErrors: 50 }, + { maxErrors: executionOptions?.maxCoercionErrors ?? 50 }, ); if (coercedVariableValues.errors) { From 610c76ad7644a6845a91636128d609a96ae9c05b Mon Sep 17 00:00:00 2001 From: Cris Naranjo Date: Fri, 4 Apr 2025 18:06:56 +0200 Subject: [PATCH 2/5] rename + update docs --- src/execution/__tests__/executor-test.ts | 8 ++--- src/execution/execute.ts | 8 +++-- website/pages/api-v16/execution.mdx | 41 ++++++++++++++++-------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 1578e78f2a..2c87fb26a0 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -1356,7 +1356,7 @@ describe('Execute: Handles basic execution tasks', () => { } `); - const executionOptions = { + const options = { maxCoercionErrors: 1, }; @@ -1371,13 +1371,11 @@ describe('Execute: Handles basic execution tasks', () => { wrongArg3: 'wrong', }, }, - executionOptions, + options, }); // Returns at least 2 errors, one for the first 'wrongArg', and one for coercion limit - expect(result.errors).to.have.lengthOf( - executionOptions.maxCoercionErrors + 1, - ); + expect(result.errors).to.have.lengthOf(options.maxCoercionErrors + 1); expect( result.errors && result.errors.length > 0 ? result.errors[result.errors.length - 1].message diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 268008f9ed..5cd64d40f9 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -152,7 +152,9 @@ export interface ExecutionArgs { fieldResolver?: Maybe>; typeResolver?: Maybe>; subscribeFieldResolver?: Maybe>; - executionOptions?: { + /** Additional execution options. */ + options?: { + /** Set the maximum number of errors allowed for coercing (defaults to 50). */ maxCoercionErrors?: number; }; } @@ -289,7 +291,7 @@ export function buildExecutionContext( fieldResolver, typeResolver, subscribeFieldResolver, - executionOptions, + options, } = args; let operation: OperationDefinitionNode | undefined; @@ -333,7 +335,7 @@ export function buildExecutionContext( schema, variableDefinitions, rawVariableValues ?? {}, - { maxErrors: executionOptions?.maxCoercionErrors ?? 50 }, + { maxErrors: options?.maxCoercionErrors ?? 50 }, ); if (coercedVariableValues.errors) { diff --git a/website/pages/api-v16/execution.mdx b/website/pages/api-v16/execution.mdx index 2810ed183a..1041c77393 100644 --- a/website/pages/api-v16/execution.mdx +++ b/website/pages/api-v16/execution.mdx @@ -29,16 +29,20 @@ const { execute } = require('graphql'); // CommonJS ### execute ```ts -export function execute( +export function execute({ schema: GraphQLSchema, - documentAST: Document, - rootValue?: mixed, - contextValue?: mixed, + document: DocumentNode, + rootValue?: unknown, + contextValue?: unknown, variableValues?: { [key: string]: mixed }, operationName?: string, -): MaybePromise; + fieldResolver?: Maybe>; + typeResolver?: Maybe>; + subscribeFieldResolver?: Maybe>; + options?: { maxCoercionErrors?: number }; +}): PromiseOrValue; -type MaybePromise = Promise | T; +type PromiseOrValue = Promise | T; interface ExecutionResult< TData = ObjMap, @@ -61,21 +65,32 @@ a GraphQLError will be thrown immediately explaining the invalid input. executing the query, `errors` is null if no errors occurred, and is a non-empty array if an error occurred. +#### options + +##### maxCoercionErrors + +Set the maximum number of errors allowed for coercing (defaults to 50). + ### executeSync ```ts -export function executeSync( +export function executeSync({ schema: GraphQLSchema, - documentAST: Document, - rootValue?: mixed, - contextValue?: mixed, + document: DocumentNode, + rootValue?: unknown, + contextValue?: unknown, variableValues?: { [key: string]: mixed }, operationName?: string, -): ExecutionResult; + fieldResolver?: Maybe>; + typeResolver?: Maybe>; + subscribeFieldResolver?: Maybe>; + options?: { maxCoercionErrors?: number }; +}): ExecutionResult; type ExecutionResult = { - data: Object; - errors?: GraphQLError[]; + errors?: ReadonlyArray; + data?: TData | null; + extensions?: TExtensions; }; ``` From e6789ef2aaed5a6277fd1789f9b4ec3463610595 Mon Sep 17 00:00:00 2001 From: Cris Naranjo Date: Sun, 6 Apr 2025 12:07:46 +0200 Subject: [PATCH 3/5] revert docs changes --- website/pages/api-v16/execution.mdx | 41 +++++++++-------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/website/pages/api-v16/execution.mdx b/website/pages/api-v16/execution.mdx index 1041c77393..2810ed183a 100644 --- a/website/pages/api-v16/execution.mdx +++ b/website/pages/api-v16/execution.mdx @@ -29,20 +29,16 @@ const { execute } = require('graphql'); // CommonJS ### execute ```ts -export function execute({ +export function execute( schema: GraphQLSchema, - document: DocumentNode, - rootValue?: unknown, - contextValue?: unknown, + documentAST: Document, + rootValue?: mixed, + contextValue?: mixed, variableValues?: { [key: string]: mixed }, operationName?: string, - fieldResolver?: Maybe>; - typeResolver?: Maybe>; - subscribeFieldResolver?: Maybe>; - options?: { maxCoercionErrors?: number }; -}): PromiseOrValue; +): MaybePromise; -type PromiseOrValue = Promise | T; +type MaybePromise = Promise | T; interface ExecutionResult< TData = ObjMap, @@ -65,32 +61,21 @@ a GraphQLError will be thrown immediately explaining the invalid input. executing the query, `errors` is null if no errors occurred, and is a non-empty array if an error occurred. -#### options - -##### maxCoercionErrors - -Set the maximum number of errors allowed for coercing (defaults to 50). - ### executeSync ```ts -export function executeSync({ +export function executeSync( schema: GraphQLSchema, - document: DocumentNode, - rootValue?: unknown, - contextValue?: unknown, + documentAST: Document, + rootValue?: mixed, + contextValue?: mixed, variableValues?: { [key: string]: mixed }, operationName?: string, - fieldResolver?: Maybe>; - typeResolver?: Maybe>; - subscribeFieldResolver?: Maybe>; - options?: { maxCoercionErrors?: number }; -}): ExecutionResult; +): ExecutionResult; type ExecutionResult = { - errors?: ReadonlyArray; - data?: TData | null; - extensions?: TExtensions; + data: Object; + errors?: GraphQLError[]; }; ``` From 0231740e5d2bf351e0c53cedf8f27b9483f9a645 Mon Sep 17 00:00:00 2001 From: Cris Naranjo Date: Sun, 6 Apr 2025 12:33:13 +0200 Subject: [PATCH 4/5] refactor test with hardcoded expected --- src/execution/__tests__/executor-test.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 2c87fb26a0..7342a7665f 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -24,7 +24,7 @@ import { GraphQLSchema } from '../../type/schema'; import { execute, executeSync } from '../execute'; -describe('Execute: Handles basic execution tasks', () => { +describe.only('Execute: Handles basic execution tasks', () => { it('throws if no document is provided', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ @@ -1376,12 +1376,19 @@ describe('Execute: Handles basic execution tasks', () => { // Returns at least 2 errors, one for the first 'wrongArg', and one for coercion limit expect(result.errors).to.have.lengthOf(options.maxCoercionErrors + 1); - expect( - result.errors && result.errors.length > 0 - ? result.errors[result.errors.length - 1].message - : undefined, - ).to.equal( - 'Too many errors processing variables, error limit reached. Execution aborted.', - ); + + expectJSON(result).toDeepEqual({ + errors: [ + { + message: + 'Variable "$data" got invalid value { email: "", wrongArg: "wrong", wrongArg2: "wrong", wrongArg3: "wrong" }; Field "wrongArg" is not defined by type "User".', + locations: [{ line: 2, column: 17 }], + }, + { + message: + 'Too many errors processing variables, error limit reached. Execution aborted.', + }, + ], + }); }); }); From 7911bc39bf9dd7b684a4f5a4eb4fab156bc3d3e8 Mon Sep 17 00:00:00 2001 From: Cris Naranjo Date: Sun, 6 Apr 2025 12:48:16 +0200 Subject: [PATCH 5/5] remove .only --- src/execution/__tests__/executor-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/execution/__tests__/executor-test.ts b/src/execution/__tests__/executor-test.ts index 7342a7665f..0f0c5b2861 100644 --- a/src/execution/__tests__/executor-test.ts +++ b/src/execution/__tests__/executor-test.ts @@ -24,7 +24,7 @@ import { GraphQLSchema } from '../../type/schema'; import { execute, executeSync } from '../execute'; -describe.only('Execute: Handles basic execution tasks', () => { +describe('Execute: Handles basic execution tasks', () => { it('throws if no document is provided', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({