diff --git a/packages/toolkit/src/query/fetchBaseQuery.ts b/packages/toolkit/src/query/fetchBaseQuery.ts index 94ee1c4d13..173f3e1bbc 100644 --- a/packages/toolkit/src/query/fetchBaseQuery.ts +++ b/packages/toolkit/src/query/fetchBaseQuery.ts @@ -4,6 +4,7 @@ import type { BaseQueryApi, BaseQueryFn } from './baseQueryTypes' import type { MaybePromise, Override } from './tsHelpers' export type ResponseHandler = + | 'content-type' | 'json' | 'text' | ((response: Response) => Promise) @@ -41,24 +42,6 @@ const defaultValidateStatus = (response: Response) => const defaultIsJsonContentType = (headers: Headers) => /*applicat*/ /ion\/(vnd\.api\+)?json/.test(headers.get('content-type') || '') -const handleResponse = async ( - response: Response, - responseHandler: ResponseHandler -) => { - if (typeof responseHandler === 'function') { - return responseHandler(response) - } - - if (responseHandler === 'text') { - return response.text() - } - - if (responseHandler === 'json') { - const text = await response.text() - return text.length ? JSON.parse(text) : null - } -} - export type FetchBaseQueryError = | { /** @@ -342,4 +325,24 @@ export function fetchBaseQuery({ meta, } } + + async function handleResponse( + response: Response, + responseHandler: ResponseHandler + ) { + if (typeof responseHandler === 'function') { + return responseHandler(response) + } + + if (responseHandler === 'content-type') { + responseHandler = isJsonContentType(response.headers) ? 'json' : 'text' + } + + if (responseHandler === 'json') { + const text = await response.text() + return text.length ? JSON.parse(text) : null + } + + return response.text() + } } diff --git a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx index 37eb638a8e..55f9ec6e6b 100644 --- a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx +++ b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx @@ -181,6 +181,64 @@ describe('fetchBaseQuery', () => { }) }) + it('success: parse text without error ("content-type" responseHandler)', async () => { + server.use( + rest.get('https://example.com/success', (_, res, ctx) => + res.once( + ctx.text(`this is not json!`) + // NOTE: MSW sets content-type header as text automatically + ) + ) + ) + + const req = baseQuery( + { + url: '/success', + responseHandler: 'content-type', + }, + commonBaseQueryApi, + {} + ) + expect(req).toBeInstanceOf(Promise) + const res = await req + expect(res).toBeInstanceOf(Object) + expect(res.meta?.response?.headers.get('content-type')).toEqual( + 'text/plain' + ) + expect(res.meta?.request).toBeInstanceOf(Request) + expect(res.meta?.response).toBeInstanceOf(Object) + expect(res.data).toEqual(`this is not json!`) + }) + + it('success: parse json without error ("content-type" responseHandler)', async () => { + server.use( + rest.get('https://example.com/success', (_, res, ctx) => + res.once( + ctx.json(`this will become json!`) + // NOTE: MSW sets content-type header as json automatically + ) + ) + ) + + const req = baseQuery( + { + url: '/success', + responseHandler: 'content-type', + }, + commonBaseQueryApi, + {} + ) + expect(req).toBeInstanceOf(Promise) + const res = await req + expect(res).toBeInstanceOf(Object) + expect(res.meta?.response?.headers.get('content-type')).toEqual( + 'application/json' + ) + expect(res.meta?.request).toBeInstanceOf(Request) + expect(res.meta?.response).toBeInstanceOf(Object) + expect(res.data).toEqual(`this will become json!`) + }) + it('server error: should fail normally with a 500 status ("text" responseHandler)', async () => { server.use( rest.get('https://example.com/error', (_, res, ctx) => @@ -204,6 +262,62 @@ describe('fetchBaseQuery', () => { }) }) + it('server error: should fail normally with a 500 status as text ("content-type" responseHandler)', async () => { + const serverResponse = 'Internal Server Error' + server.use( + rest.get('https://example.com/error', (_, res, ctx) => + res(ctx.status(500), ctx.text(serverResponse)) + ) + ) + + const req = baseQuery( + { url: '/error', responseHandler: 'content-type' }, + commonBaseQueryApi, + {} + ) + expect(req).toBeInstanceOf(Promise) + const res = await req + expect(res).toBeInstanceOf(Object) + expect(res.meta?.request).toBeInstanceOf(Request) + expect(res.meta?.response).toBeInstanceOf(Object) + expect(res.meta?.response?.headers.get('content-type')).toEqual( + 'text/plain' + ) + expect(res.error).toEqual({ + status: 500, + data: serverResponse, + }) + }) + + it('server error: should fail normally with a 500 status as json ("content-type" responseHandler)', async () => { + const serverResponse = { + errors: { field1: "Password cannot be 'password'" }, + } + server.use( + rest.get('https://example.com/error', (_, res, ctx) => + res(ctx.status(500), ctx.json(serverResponse)) + ) + ) + + const req = baseQuery( + { url: '/error', responseHandler: 'content-type' }, + commonBaseQueryApi, + {} + ) + expect(req).toBeInstanceOf(Promise) + const res = await req + expect(res).toBeInstanceOf(Object) + expect(res.meta?.request).toBeInstanceOf(Request) + expect(res.meta?.response).toBeInstanceOf(Object) + expect(res.meta?.response?.headers.get('content-type')).toEqual( + 'application/json' + ) + expect(res.error).toEqual({ + status: 500, + data: serverResponse, + }) + }) + it('server error: should fail gracefully (default="json" responseHandler)', async () => { server.use( rest.get('https://example.com/error', (_, res, ctx) =>