diff --git a/.changeset/warm-zebras-push.md b/.changeset/warm-zebras-push.md new file mode 100644 index 00000000000..e48326ba85b --- /dev/null +++ b/.changeset/warm-zebras-push.md @@ -0,0 +1,9 @@ +--- +"@apollo/client": patch +--- + +Fix type signature of `ServerError`. + +In <3.7 `HttpLink` and `BatchHttpLink` would return a `ServerError.message` of e.g. `"Unexpected token 'E', \"Error! Foo bar\" is not valid JSON"` and a `ServerError.result` of `undefined` in the case where a server returned a >= 300 response code with a response body containing a string that could not be parsed as JSON. + +In >=3.7, `message` became e.g. `Response not successful: Received status code 302` and `result` became the string from the response body, however the type in `ServerError.result` was not updated to include the `string` type, which is now properly reflected. diff --git a/src/link/http/__tests__/HttpLink.ts b/src/link/http/__tests__/HttpLink.ts index dd7ffd9b8cf..c72e254f809 100644 --- a/src/link/http/__tests__/HttpLink.ts +++ b/src/link/http/__tests__/HttpLink.ts @@ -1083,6 +1083,11 @@ describe('HttpLink', () => { responseBody = JSON.parse(responseBodyText); return Promise.resolve(responseBodyText); }); + const textWithStringError = jest.fn(() => { + const responseBodyText = 'Error! Foo bar'; + responseBody = responseBodyText; + return Promise.resolve(responseBodyText); + }); const textWithData = jest.fn(() => { responseBody = { data: { stub: { id: 1 } }, @@ -1151,6 +1156,20 @@ describe('HttpLink', () => { }), ); }); + itAsync('throws an error if response code is > 300 and handles string response body', (resolve, reject) => { + fetch.mockReturnValueOnce(Promise.resolve({ status: 302, text: textWithStringError })); + const link = createHttpLink({ uri: 'data', fetch: fetch as any }); + execute(link, { query: sampleQuery }).subscribe( + result => { + reject('next should have been thrown from the network'); + }, + makeCallback(resolve, reject, (e: ServerError) => { + expect(e.message).toMatch(/Received status code 302/); + expect(e.statusCode).toBe(302); + expect(e.result).toEqual(responseBody); + }), + ); + }); itAsync('throws an error if response code is > 300 and returns data', (resolve, reject) => { fetch.mockReturnValueOnce( Promise.resolve({ status: 400, text: textWithData }), @@ -1180,7 +1199,6 @@ describe('HttpLink', () => { ); const link = createHttpLink({ uri: 'data', fetch: fetch as any }); - execute(link, { query: sampleQuery }).subscribe( result => { reject('should not have called result because we have no data'); diff --git a/src/link/http/parseAndCheckHttpResponse.ts b/src/link/http/parseAndCheckHttpResponse.ts index 53e30c29082..ba9307ac9e2 100644 --- a/src/link/http/parseAndCheckHttpResponse.ts +++ b/src/link/http/parseAndCheckHttpResponse.ts @@ -136,7 +136,7 @@ export function parseHeaders(headerText: string): Record { export function parseJsonBody(response: Response, bodyText: string): T { if (response.status >= 300) { // Network error - const getResult = () => { + const getResult = (): Record | string => { try { return JSON.parse(bodyText); } catch (err) { diff --git a/src/link/persisted-queries/index.ts b/src/link/persisted-queries/index.ts index 1aa661918dc..3123dc7bdec 100644 --- a/src/link/persisted-queries/index.ts +++ b/src/link/persisted-queries/index.ts @@ -177,10 +177,13 @@ export const createPersistedQueryLink = ( } // Network errors can return GraphQL errors on for example a 403 - const networkErrors = - networkError && - networkError.result && - networkError.result.errors as GraphQLError[]; + let networkErrors; + if (typeof networkError?.result !== 'string') { + networkErrors = + networkError && + networkError.result && + networkError.result.errors as GraphQLError[]; + } if (isNonEmptyArray(networkErrors)) { graphQLErrors.push(...networkErrors); } diff --git a/src/link/utils/throwServerError.ts b/src/link/utils/throwServerError.ts index b9283d16cd3..ed11631ec1b 100644 --- a/src/link/utils/throwServerError.ts +++ b/src/link/utils/throwServerError.ts @@ -1,6 +1,6 @@ export type ServerError = Error & { response: Response; - result: Record; + result: Record | string; statusCode: number; };