Skip to content

Commit

Permalink
feat(UserInputError): handle unhandled errors. (#5508)
Browse files Browse the repository at this point in the history
Co-authored-by: David Glasser <glasser@davidglasser.net>
  • Loading branch information
5achinJani and glasser committed Jul 21, 2021
1 parent e030e02 commit 12f46fc
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 5 deletions.
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

0 comments on commit 12f46fc

Please sign in to comment.