Skip to content

feat(execution): add max coercion errors option to execution context #4366

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

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/execution/__tests__/executor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Kind } from '../../language/kinds';
import { parse } from '../../language/parser';

import {
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLList,
GraphQLNonNull,
Expand Down Expand Up @@ -1320,4 +1321,67 @@ 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 options = {
maxCoercionErrors: 1,
};

const result = executeSync({
schema,
document,
variableValues: {
data: {
email: '',
wrongArg: 'wrong',
wrongArg2: 'wrong',
wrongArg3: 'wrong',
},
},
options,
});

// 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.',
);
});
});
8 changes: 7 additions & 1 deletion src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ export interface ExecutionArgs {
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
/** Additional execution options. */
options?: {
/** Set the maximum number of errors allowed for coercing (defaults to 50). */
maxCoercionErrors?: number;
};
}

/**
Expand Down Expand Up @@ -286,6 +291,7 @@ export function buildExecutionContext(
fieldResolver,
typeResolver,
subscribeFieldResolver,
options,
} = args;

let operation: OperationDefinitionNode | undefined;
Expand Down Expand Up @@ -329,7 +335,7 @@ export function buildExecutionContext(
schema,
variableDefinitions,
rawVariableValues ?? {},
{ maxErrors: 50 },
{ maxErrors: options?.maxCoercionErrors ?? 50 },
);

if (coercedVariableValues.errors) {
Expand Down
41 changes: 28 additions & 13 deletions website/pages/api-v16/execution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,20 @@ const { execute } = require('graphql'); // CommonJS
### execute

```ts
export function execute(
export function execute({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this object syntax wasn't intended

schema: GraphQLSchema,
documentAST: Document,
rootValue?: mixed,
contextValue?: mixed,
document: DocumentNode,
rootValue?: unknown,
contextValue?: unknown,
variableValues?: { [key: string]: mixed },
operationName?: string,
): MaybePromise<ExecutionResult>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
options?: { maxCoercionErrors?: number };
}): PromiseOrValue<ExecutionResult>;

type MaybePromise<T> = Promise<T> | T;
type PromiseOrValue<T> = Promise<T> | T;

interface ExecutionResult<
TData = ObjMap<unknown>,
Expand All @@ -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).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we leave the rest of the docs untouched, the changes seem invalid 😅 like from where I sit the only thing that we need to add is options?: { maxCoercionErrors?: number }; as an argument, not an object-field to the docs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are wrong. I cannot add options? as a positional argument without adding the other missing arguments (fieldResolver, typeResolver, subscribeFieldResolver). I don't know why the function accepts positional arguments or an object, I guess to avoid a breaking change, but the code looks like prefer the object solution and the documentation should reflect that.
I might be wrong but I think my changes make sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code:

'graphql@16 dropped long-deprecated support for positional arguments, please pass an object instead.',

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair but the docs should still show both methods rather than this kind of driveby change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reverted the changes to the docs. My only concern now is that this new options are only documented in the code, so not sure how many people will benefit from them...

It would be nice to update the docs for v17.


### 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<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
options?: { maxCoercionErrors?: number };
}): ExecutionResult;

type ExecutionResult = {
data: Object;
errors?: GraphQLError[];
errors?: ReadonlyArray<GraphQLError>;
data?: TData | null;
extensions?: TExtensions;
};
```

Expand Down