From 85c43241969f992c5858dc29ee56604823aea85a Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Thu, 3 Oct 2019 04:26:49 +0000 Subject: [PATCH 1/8] feat: throw a descriptive error (#823) --- .../graphql-client/src/GraphQLErrorList.ts | 20 +++++++++++++++++++ packages/graphql-client/src/graphql-client.ts | 18 ++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 packages/graphql-client/src/GraphQLErrorList.ts diff --git a/packages/graphql-client/src/GraphQLErrorList.ts b/packages/graphql-client/src/GraphQLErrorList.ts new file mode 100644 index 000000000..ff3d4a768 --- /dev/null +++ b/packages/graphql-client/src/GraphQLErrorList.ts @@ -0,0 +1,20 @@ +import { GraphQLError } from 'graphql'; + +export class GraphQLErrorList extends Error { + public errors: readonly GraphQLError[]; + + constructor(errors: readonly GraphQLError[], message?: string) { + super(); + this.errors = errors; + this.stack = new Error().stack; + + const br = '\r\n'; + const errList = errors.map(err => `\t- ${err.message}`).join(br); + const additionalMsg = message ? ' ' + message : ':'; + this.message = `GraphQLErrorList - ${errors.length} errors${additionalMsg}${br}${errList}`; + } + + toString() { + return this.errors.map(err => err.toString()).join('\r\n'); + } +} diff --git a/packages/graphql-client/src/graphql-client.ts b/packages/graphql-client/src/graphql-client.ts index accdc2481..1216693a1 100644 --- a/packages/graphql-client/src/graphql-client.ts +++ b/packages/graphql-client/src/graphql-client.ts @@ -16,6 +16,8 @@ import { twoFactorUnsetMutation } from './graphql/two-factor-unset.mutation'; import { impersonateMutation } from './graphql/impersonate.mutation'; import { getUserQuery } from './graphql/get-user.query'; import gql from 'graphql-tag'; +import { GraphQLErrorList } from './GraphQLErrorList'; +import { print } from 'graphql'; export interface IAuthenticateParams { [key: string]: string | object; @@ -166,14 +168,14 @@ export default class GraphQLClient implements TransportInterface { } private async mutate(mutation: any, resultField: any, variables: any = {}) { - // If we are executiong a refresh token mutation do not call refress session again + // If we are executing a refresh token mutation do not call refresh session again // otherwise it will end up in an infinite loop const tokens = mutation === refreshTokensMutation ? await this.client.getTokens() : await this.client.refreshSession(); - const { data } = await this.options.graphQLClient.mutate({ + const { data, errors } = await this.options.graphQLClient.mutate({ mutation, variables, context: { @@ -182,13 +184,18 @@ export default class GraphQLClient implements TransportInterface { }, }, }); + + if (errors) { + throw new GraphQLErrorList(errors, `in mutation: \r\n ${print(mutation)}`); + } + return data[resultField]; } private async query(query: any, resultField: any, variables: any = {}) { const tokens = await this.client.refreshSession(); - const { data } = await this.options.graphQLClient.query({ + const { data, errors } = await this.options.graphQLClient.query({ query, variables, fetchPolicy: 'network-only', @@ -198,6 +205,11 @@ export default class GraphQLClient implements TransportInterface { }, }, }); + + if (errors) { + throw new GraphQLErrorList(errors, `in query: \r\n ${print(query)}`); + } + return data[resultField]; } } From fb3e726be5cb187ebd717465d5a3126f198c4621 Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Thu, 3 Oct 2019 04:50:52 +0000 Subject: [PATCH 2/8] update error formatting --- packages/graphql-client/src/GraphQLErrorList.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/graphql-client/src/GraphQLErrorList.ts b/packages/graphql-client/src/GraphQLErrorList.ts index ff3d4a768..e84fb9918 100644 --- a/packages/graphql-client/src/GraphQLErrorList.ts +++ b/packages/graphql-client/src/GraphQLErrorList.ts @@ -9,9 +9,11 @@ export class GraphQLErrorList extends Error { this.stack = new Error().stack; const br = '\r\n'; + const summary = `${errors.length} error${errors.length > 1 && 's'}${ + message ? ' ' + message : ':' + }`; const errList = errors.map(err => `\t- ${err.message}`).join(br); - const additionalMsg = message ? ' ' + message : ':'; - this.message = `GraphQLErrorList - ${errors.length} errors${additionalMsg}${br}${errList}`; + this.message = `GraphQLErrorList - ${summary}${br}${errList}`; } toString() { From c90c417d3dba12fe9a7ef010c1407abf83759ab6 Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Fri, 4 Oct 2019 03:53:00 +0000 Subject: [PATCH 3/8] export GraphQLErrorList --- packages/graphql-client/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/graphql-client/src/index.ts b/packages/graphql-client/src/index.ts index 933ac76a9..d448156ea 100644 --- a/packages/graphql-client/src/index.ts +++ b/packages/graphql-client/src/index.ts @@ -1,3 +1,3 @@ export * from './graphql-client'; -export { default } from './graphql-client'; -export { default as AccountsGraphQLClient } from './graphql-client'; +export { default as AccountsGraphQLClient, default } from './graphql-client'; +export * from './GraphQLErrorList'; From 3dc7560a07a2b3b7ed22326d71ca028b516b4ef8 Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Fri, 4 Oct 2019 04:00:22 +0000 Subject: [PATCH 4/8] fix: formatting of message for single error --- packages/graphql-client/src/GraphQLErrorList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphql-client/src/GraphQLErrorList.ts b/packages/graphql-client/src/GraphQLErrorList.ts index e84fb9918..603ba7f12 100644 --- a/packages/graphql-client/src/GraphQLErrorList.ts +++ b/packages/graphql-client/src/GraphQLErrorList.ts @@ -9,7 +9,7 @@ export class GraphQLErrorList extends Error { this.stack = new Error().stack; const br = '\r\n'; - const summary = `${errors.length} error${errors.length > 1 && 's'}${ + const summary = `${errors.length} error${errors.length > 1 ? 's' : ''}${ message ? ' ' + message : ':' }`; const errList = errors.map(err => `\t- ${err.message}`).join(br); From 2bd28ceae1fa9195971e7469f55919b7fd96d155 Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Fri, 4 Oct 2019 04:00:47 +0000 Subject: [PATCH 5/8] fix: impersonate mutation query emails array --- packages/graphql-client/src/graphql/impersonate.mutation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/graphql-client/src/graphql/impersonate.mutation.ts b/packages/graphql-client/src/graphql/impersonate.mutation.ts index 2d6069ebb..24e0e6fc9 100644 --- a/packages/graphql-client/src/graphql/impersonate.mutation.ts +++ b/packages/graphql-client/src/graphql/impersonate.mutation.ts @@ -11,7 +11,10 @@ export const impersonateMutation = gql` # // TODO: Extract user into a fragment user { id - email + emails { + address + verified + } username } } From 8bd68a3e4129f2824764515796e37cff99879d3c Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Thu, 17 Oct 2019 03:36:41 +0000 Subject: [PATCH 6/8] docs: add docs example for GraphQLErrorList --- website/docs/transports/graphql.md | 56 ++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/website/docs/transports/graphql.md b/website/docs/transports/graphql.md index d637bf026..931f301df 100644 --- a/website/docs/transports/graphql.md +++ b/website/docs/transports/graphql.md @@ -286,14 +286,66 @@ yarn add @accounts/graphql-client import { ApolloClient } from 'apollo-client'; import { AccountsGraphQLClient } from '@accounts/graphql-client'; +// Create Apollo client const apolloClient = new ApolloClient({ // apollo options }); -const accountsGraphQL = new GraphQLClient({ +// Create your transport +const accountsGraphQL = new AccountsGraphQLClient({ graphQLClient: apolloClient, - // other options + //other options like 'userFieldsFragment' }); + +// Create the AccountsClient +const accountsClient = new AccountsClient( + { + // accountsClient Options + }, + accountsGraphQL +); + +// Create service client +const passwordClient = new AccountsClientPassword(accountsClient); +``` + +### Error Handling + +The AccountsGraphQLClient will throw errors when the graphql query/mutation returns them. Because there could be multiple `GraphQLError`s, these errors will be wrapped into a `GraphQLErrorList` object. + +```js +async function registerUser() { + try { + passwordClient.createUser({ email: 'foo@foobar.com', password: 'foo' }); + } catch (e) { + if (e instanceof GraphQLErrorList) { + // the message will format the errors in a list + console.log(error.message); + /* example: + GraphQLErrorList - 1 errors in mutation: + mutation impersonate($accessToken: String!, $username: String!) { + impersonate(accessToken: $accessToken, username: $username) { + authorized + tokens { + refreshToken + accessToken + } + user { + id + email + username + } + } + } + + - Cannot query field "email" on type "User". Did you mean "emails"? + */ + + // Alternatively, the list of errors is accessible on the "errors" property + error.errors.forEach(graphQLError => console.log(graphQLError)); + } + } +} ``` ## Using with Apollo Link From bd6c6f1146b3ab5402c7c0d46b6689c68bc30367 Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Thu, 17 Oct 2019 03:40:20 +0000 Subject: [PATCH 7/8] docs: update imports in error handling example --- website/docs/transports/graphql.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/transports/graphql.md b/website/docs/transports/graphql.md index 931f301df..b4c147cd0 100644 --- a/website/docs/transports/graphql.md +++ b/website/docs/transports/graphql.md @@ -284,6 +284,8 @@ yarn add @accounts/graphql-client ```js import { ApolloClient } from 'apollo-client'; +import { AccountsClient } from '@accounts/client'; +import { AccountsClientPassword } from '@accounts/client-password'; import { AccountsGraphQLClient } from '@accounts/graphql-client'; // Create Apollo client @@ -314,6 +316,8 @@ const passwordClient = new AccountsClientPassword(accountsClient); The AccountsGraphQLClient will throw errors when the graphql query/mutation returns them. Because there could be multiple `GraphQLError`s, these errors will be wrapped into a `GraphQLErrorList` object. ```js +import { GraphQLErrorList } from '@accounts/graphql-client'; + async function registerUser() { try { passwordClient.createUser({ email: 'foo@foobar.com', password: 'foo' }); From 7e878602a7c257d0a295cb31a0380d68f76bcb38 Mon Sep 17 00:00:00 2001 From: Nick Bolles Date: Tue, 4 Feb 2020 14:29:51 +0000 Subject: [PATCH 8/8] fix compile error --- packages/graphql-client/src/graphql-client.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/graphql-client/src/graphql-client.ts b/packages/graphql-client/src/graphql-client.ts index 0390f28d6..cad956860 100644 --- a/packages/graphql-client/src/graphql-client.ts +++ b/packages/graphql-client/src/graphql-client.ts @@ -1,5 +1,6 @@ import { AccountsClient, TransportInterface } from '@accounts/client'; import { CreateUser, ImpersonationResult, LoginResult, User } from '@accounts/types'; +import { print } from 'graphql'; import gql from 'graphql-tag'; import { authenticateWithServiceMutation } from './graphql/authenticate-with-service.mutation'; import { changePasswordMutation } from './graphql/change-password.mutation'; @@ -15,11 +16,8 @@ import { sendResetPasswordEmailMutation } from './graphql/send-reset-password-em import { sendVerificationEmailMutation } from './graphql/send-verification-email.mutation'; import { twoFactorSetMutation } from './graphql/two-factor-set.mutation'; import { twoFactorUnsetMutation } from './graphql/two-factor-unset.mutation'; -import { impersonateMutation } from './graphql/impersonate.mutation'; -import { getUserQuery } from './graphql/get-user.query'; -import { GraphQLErrorList } from './GraphQLErrorList'; -import { print } from 'graphql'; import { verifyEmailMutation } from './graphql/verify-email.mutation'; +import { GraphQLErrorList } from './GraphQLErrorList'; export interface AuthenticateParams { [key: string]: string | object;