From 5e4f6ff6b15bd366b47ed5d4a016d24ce6a8a133 Mon Sep 17 00:00:00 2001 From: Ivan Motiienko <1161259+katsanva@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:46:04 +0100 Subject: [PATCH] Allow typing the body of a `RequestError` response (#2325) --- source/core/errors.ts | 9 +++++---- source/core/index.ts | 1 + test/error.ts | 29 +++++++++++++++++++++++++---- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/source/core/errors.ts b/source/core/errors.ts index d344517da..07e50705f 100644 --- a/source/core/errors.ts +++ b/source/core/errors.ts @@ -16,13 +16,13 @@ function isRequest(x: unknown): x is Request { An error to be thrown when a request fails. Contains a `code` property with error class code, like `ECONNREFUSED`. */ -export class RequestError extends Error { +export class RequestError extends Error { input?: string; code: string; override stack!: string; declare readonly options: Options; - readonly response?: Response; + readonly response?: Response; readonly request?: Request; readonly timings?: Timings; @@ -88,9 +88,10 @@ export class MaxRedirectsError extends RequestError { An error to be thrown when the server response code is not 2xx nor 3xx if `options.followRedirect` is `true`, but always except for 304. Includes a `response` property. */ +// TODO: Change `HTTPError` to `HTTPError` in the next major version to enforce type usage. // eslint-disable-next-line @typescript-eslint/naming-convention -export class HTTPError extends RequestError { - declare readonly response: Response; +export class HTTPError extends RequestError { + declare readonly response: Response; declare readonly request: Request; declare readonly timings: Timings; diff --git a/source/core/index.ts b/source/core/index.ts index 36e5cf862..fc2858bfc 100644 --- a/source/core/index.ts +++ b/source/core/index.ts @@ -701,6 +701,7 @@ export default class Request extends Duplex implements RequestEvents { let promises: Array> = rawCookies.map(async (rawCookie: string) => (options.cookieJar as PromiseCookieJar).setCookie(rawCookie, url!.toString())); if (options.ignoreInvalidCookies) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises promises = promises.map(async promise => { try { await promise; diff --git a/test/error.ts b/test/error.ts index 637403514..d915de231 100644 --- a/test/error.ts +++ b/test/error.ts @@ -20,7 +20,7 @@ test('properties', withServer, async (t, server, got) => { const url = new URL(server.url); - const error = (await t.throwsAsync(got('')))!; + const error = (await t.throwsAsync>(got('')))!; t.truthy(error); t.truthy(error.response); t.truthy(error.options); @@ -30,11 +30,12 @@ test('properties', withServer, async (t, server, got) => { t.is(error.message, 'Response code 404 (Not Found)'); t.deepEqual(error.options.url, url); t.is(error.response.headers.connection, 'keep-alive'); - t.is(error.response.body, 'not'); + // Assert is used for type checking + t.assert(error.response.body === 'not'); }); test('catches dns errors', async t => { - const error = (await t.throwsAsync(got('http://doesntexist', {retry: {limit: 0}})))!; + const error = (await t.throwsAsync>(got('http://doesntexist', {retry: {limit: 0}})))!; t.truthy(error); t.regex(error.message, /ENOTFOUND|EAI_AGAIN/); t.is((error.options.url as URL).host, 'doesntexist'); @@ -110,7 +111,27 @@ test('custom body', withServer, async (t, server, got) => { message: 'Response code 404 (Not Found)', }); t.is(error?.response.statusCode, 404); - t.is(error?.response.body, 'not'); + // Typecheck for default `any` type + t.assert(error?.response.body === 'not'); +}); + +test('custom json body', withServer, async (t, server, got) => { + server.get('/', (_request, response) => { + response.statusCode = 404; + response.header('content-type', 'application/json'); + response.end(JSON.stringify({ + message: 'not found', + })); + }); + + const error = await t.throwsAsync>(got('', {responseType: 'json'}), + { + instanceOf: HTTPError, + message: 'Response code 404 (Not Found)', + }); + t.is(error?.response.statusCode, 404); + // Assert is used for body typecheck + t.assert(error?.response.body.message === 'not found'); }); test('contains Got options', withServer, async (t, server, got) => {