Skip to content
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

feat(UserInputError): handle unhandled errors. #5508

Merged
merged 2 commits into from Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,9 @@ The version headers in this history reflect the versions of Apollo Server itself

## vNEXT

- `apollo-server-core`: If a client does not provide a value or provides null for a variable declared to be non-null, this is now reported as an error with an `extensions.code` of `BAD_USER_INPUT` rather than `INTERNAL_SERVER_ERROR`. (This is similar to a change we made in v2.23.0 for variables that are sent as the wrong type.) [PR #5508](https://github.com/apollographql/apollo-server/pull/5508) [Issue #5353](https://github.com/apollographql/apollo-server/issues/5353)


## v3.0.2

- `apollo-server-types`: TypeScript typings for `info.cacheControl` are now added to `GraphQLResolveInfo` as part of `apollo-server-types` rather than a nested file in `apollo-server-core`, and the field now has a named type, `ResolveInfoCacheControl`. [PR #5512](https://github.com/apollographql/apollo-server/pull/5512)
Expand Down
26 changes: 21 additions & 5 deletions packages/apollo-server-core/src/requestPipeline.ts
Expand Up @@ -378,16 +378,32 @@ export async function processGraphQLRequest<TContext>(

// The first thing that execution does is coerce the request's variables
// to the types declared in the operation, which can lead to errors if
// they are of the wrong type. We change any such errors into
// UserInputError so that their code doesn't end up being
// INTERNAL_SERVER_ERROR, since these are client errors.
// they are of the wrong type. It also makes sure that all non-null
// variables are required and get non-null values. If any of these thingss
// lead to errors, we change them into UserInputError so that their code
// doesn't end up being INTERNAL_SERVER_ERROR, since these are client
// errors.
//
// This is hacky! Hopefully graphql-js will give us a way to separate
// variable resolution from execution later; see
// https://github.com/graphql/graphql-js/issues/3169
const resultErrors = result.errors?.map((e) => {
if (
e.nodes?.length === 1 &&
e.nodes[0].kind === Kind.VARIABLE_DEFINITION &&
e.message.startsWith(
(e.message.startsWith(
`Variable "$${e.nodes[0].variable.name.value}" got invalid value `,
)
) ||
(e.nodes[0].type.kind === Kind.NON_NULL_TYPE &&
e.nodes[0].type.type.kind === Kind.NAMED_TYPE &&
(e.message.startsWith(
`Variable "$${e.nodes[0].variable.name.value}" of required ` +
`type "${e.nodes[0].type.type.name.value}!" was not provided.`,
) ||
e.message.startsWith(
`Variable "$${e.nodes[0].variable.name.value}" of non-null ` +
`type "${e.nodes[0].type.type.name.value}!" must not be null.`,
))))
) {
return fromGraphQLError(e, {
errorClass: UserInputError,
Expand Down
45 changes: 45 additions & 0 deletions packages/apollo-server-integration-testsuite/src/ApolloServer.ts
Expand Up @@ -343,6 +343,51 @@ export function testApolloServer<AS extends ApolloServerBase>(
expect(result.errors[0].extensions.code).toBe('BAD_USER_INPUT');
});

it('catches required type variable error and returns UserInputError', async () => {
const { url: uri } = await createApolloServer({
typeDefs: gql`
type Query {
hello(x: String!): String
}
`,
});

const apolloFetch = createApolloFetch({ uri });

const result = await apolloFetch({
query: `query ($x:String!) {hello(x:$x)}`,
});
expect(result.data).toBeUndefined();
expect(result.errors).toBeDefined();
expect(result.errors[0].message).toMatch(
`Variable "$x" of required type "String!" was not provided.`,
);
expect(result.errors[0].extensions.code).toBe('BAD_USER_INPUT');
});

it('catches non-null type variable error and returns UserInputError', async () => {
const { url: uri } = await createApolloServer({
typeDefs: gql`
type Query {
hello(x: String!): String
}
`,
});

const apolloFetch = createApolloFetch({ uri });

const result = await apolloFetch({
query: `query ($x:String!) {hello(x:$x)}`,
variables: { x: null },
});
expect(result.data).toBeUndefined();
expect(result.errors).toBeDefined();
expect(result.errors[0].message).toMatch(
`Variable "$x" of non-null type "String!" must not be null.`,
);
expect(result.errors[0].extensions.code).toBe('BAD_USER_INPUT');
});

describe('schema creation', () => {
it('accepts typeDefs and resolvers', async () => {
const typeDefs = gql`
Expand Down