From f27e8d331627074e76e4a9ec85a2c8d0a0625e5f Mon Sep 17 00:00:00 2001 From: Szymon Marczak <36894700+szmarczak@users.noreply.github.com> Date: Thu, 1 Jul 2021 23:17:28 +0200 Subject: [PATCH] Merge #1739 into Got 12 --- source/as-promise/types.ts | 1 + source/core/calculate-retry-delay.ts | 2 +- source/core/errors.ts | 9 +++++++-- source/core/options.ts | 5 ++++- source/core/response.ts | 1 + test/cache.ts | 5 ++++- test/cancel.ts | 30 ++++++++++++++++++++++------ test/error.ts | 9 ++++++--- test/http.ts | 6 ++++-- test/post.ts | 3 ++- test/promise.ts | 10 ++++++++-- test/redirects.ts | 8 ++++++-- test/response-parse.ts | 5 ++++- test/retry.ts | 2 +- 14 files changed, 73 insertions(+), 23 deletions(-) diff --git a/source/as-promise/types.ts b/source/as-promise/types.ts index 2633bccea..d54c03403 100644 --- a/source/as-promise/types.ts +++ b/source/as-promise/types.ts @@ -15,6 +15,7 @@ export class CancelError extends RequestError { constructor(request: Request) { super('Promise was canceled', {}, request); this.name = 'CancelError'; + this.code = 'ERR_CANCELED'; } get isCanceled() { diff --git a/source/core/calculate-retry-delay.ts b/source/core/calculate-retry-delay.ts index 3f13b81fe..75768359a 100644 --- a/source/core/calculate-retry-delay.ts +++ b/source/core/calculate-retry-delay.ts @@ -18,7 +18,7 @@ const calculateRetryDelay: Returns = ({ } const hasMethod = retryOptions.methods.includes(error.options.method); - const hasErrorCode = retryOptions.errorCodes.includes(error.code!); + const hasErrorCode = retryOptions.errorCodes.includes(error.code); const hasStatusCode = error.response && retryOptions.statusCodes.includes(error.response.statusCode); if (!hasMethod || (!hasErrorCode && !hasStatusCode)) { return 0; diff --git a/source/core/errors.ts b/source/core/errors.ts index a2ca33c1f..af3f0aa72 100644 --- a/source/core/errors.ts +++ b/source/core/errors.ts @@ -19,7 +19,7 @@ Contains a `code` property with error class code, like `ECONNREFUSED`. export class RequestError extends Error { input?: string; - code?: string; + code: string; stack!: string; declare readonly options: Options; readonly response?: Response; @@ -31,7 +31,7 @@ export class RequestError extends Error { Error.captureStackTrace(this, this.constructor); this.name = 'RequestError'; - this.code = error.code; + this.code = error.code ?? 'ERR_GOT_REQUEST_ERROR'; this.input = (error as any).input; if (isRequest(self)) { @@ -80,6 +80,7 @@ export class MaxRedirectsError extends RequestError { constructor(request: Request) { super(`Redirected ${request.options.maxRedirects} times. Aborting.`, {}, request); this.name = 'MaxRedirectsError'; + this.code = 'ERR_TOO_MANY_REDIRECTS'; } } @@ -95,6 +96,7 @@ export class HTTPError extends RequestError { constructor(response: PlainResponse) { super(`Response code ${response.statusCode} (${response.statusMessage!})`, {}, response.request); this.name = 'HTTPError'; + this.code = 'ERR_NON_2XX_3XX_RESPONSE'; } } @@ -108,6 +110,7 @@ export class CacheError extends RequestError { constructor(error: Error, request: Request) { super(error.message, error, request); this.name = 'CacheError'; + this.code = this.code === 'ERR_GOT_REQUEST_ERROR' ? 'ERR_CACHE_ACCESS' : this.code; } } @@ -120,6 +123,7 @@ export class UploadError extends RequestError { constructor(error: Error, request: Request) { super(error.message, error, request); this.name = 'UploadError'; + this.code = this.code === 'ERR_GOT_REQUEST_ERROR' ? 'ERR_UPLOAD' : this.code; } } @@ -151,6 +155,7 @@ export class ReadError extends RequestError { constructor(error: Error, request: Request) { super(error.message, error, request); this.name = 'ReadError'; + this.code = this.code === 'ERR_GOT_REQUEST_ERROR' ? 'ERR_READING_RESPONSE_STREAM' : this.code; } } diff --git a/source/core/options.ts b/source/core/options.ts index 0b93beeb5..aa43ee1c3 100644 --- a/source/core/options.ts +++ b/source/core/options.ts @@ -1166,7 +1166,10 @@ export default class Options { } if (url.protocol !== 'http:' && url.protocol !== 'https:') { - throw new Error(`Unsupported protocol: ${url.protocol}`); + const error: NodeJS.ErrnoException = new Error(`Unsupported protocol: ${url.protocol}`); + error.code = 'ERR_UNSUPPORTED_PROTOCOL'; + + throw error; } if (this._internals.username) { diff --git a/source/core/response.ts b/source/core/response.ts index 5a20bbcce..ccd286285 100644 --- a/source/core/response.ts +++ b/source/core/response.ts @@ -123,6 +123,7 @@ export class ParseError extends RequestError { super(`${error.message} in "${options.url!.toString()}"`, error, response.request); this.name = 'ParseError'; + this.code = 'ERR_BODY_PARSE_FAILURE'; } } diff --git a/test/cache.ts b/test/cache.ts index 77c713abb..729f8771b 100644 --- a/test/cache.ts +++ b/test/cache.ts @@ -121,7 +121,10 @@ test('cache error throws `CacheError`', withServer, async (t, server, got) => { const cache = {}; // @ts-expect-error Error tests - await t.throwsAsync(got({cache}), {instanceOf: CacheError}); + await t.throwsAsync(got({cache}), { + instanceOf: CacheError, + code: 'ERR_CACHE_ACCESS' + }); }); test('doesn\'t cache response when received HTTP error', withServer, async (t, server, got) => { diff --git a/test/cancel.ts b/test/cancel.ts index b7df115c4..7cd8a9098 100644 --- a/test/cancel.ts +++ b/test/cancel.ts @@ -78,7 +78,10 @@ test.serial('does not retry after cancelation', withServerAndFakeTimers, async ( gotPromise.cancel(); }); - await t.throwsAsync(gotPromise, {instanceOf: CancelError}); + await t.throwsAsync(gotPromise, { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }); await t.notThrowsAsync(promise, 'Request finished instead of aborting.'); }); @@ -105,7 +108,10 @@ test.serial('cleans up request timeouts', withServer, async (t, server, got) => } }); - await t.throwsAsync(gotPromise, {instanceOf: CancelError}); + await t.throwsAsync(gotPromise, { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }); // Wait for unhandled errors await delay(40); @@ -127,7 +133,10 @@ test.serial('cancels in-progress request', withServerAndFakeTimers, async (t, se body.push(null); }); - await t.throwsAsync(gotPromise, {instanceOf: CancelError}); + await t.throwsAsync(gotPromise, { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }); await t.notThrowsAsync(promise, 'Request finished instead of aborting.'); }); @@ -147,7 +156,10 @@ test.serial('cancels in-progress request with timeout', withServerAndFakeTimers, body.push(null); }); - await t.throwsAsync(gotPromise, {instanceOf: CancelError}); + await t.throwsAsync(gotPromise, { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }); await t.notThrowsAsync(promise, 'Request finished instead of aborting.'); }); @@ -227,7 +239,10 @@ test.serial('throws on incomplete (canceled) response - promise #2', withServer, promise.cancel(); }, 500); - await t.throwsAsync(promise, {instanceOf: CancelError}); + await t.throwsAsync(promise, { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }); }); test.serial('throws on incomplete (canceled) response - stream', withServerAndFakeTimers, async (t, server, got, clock) => { @@ -255,5 +270,8 @@ test('throws when canceling cached request', withServer, async (t, server, got) const promise = got({cache}); promise.cancel(); - await t.throwsAsync(promise, {instanceOf: CancelError}); + await t.throwsAsync(promise, { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }); }); diff --git a/test/error.ts b/test/error.ts index 6ea6492a2..ea2ed378f 100644 --- a/test/error.ts +++ b/test/error.ts @@ -27,7 +27,7 @@ test('properties', withServer, async (t, server, got) => { t.truthy(error.options); t.true({}.propertyIsEnumerable.call(error, 'options')); t.false({}.propertyIsEnumerable.call(error, 'response')); - t.is(error.code, undefined); + t.is(error.code, 'ERR_NON_2XX_3XX_RESPONSE'); t.is(error.message, 'Response code 404 (Not Found)'); t.deepEqual(error.options.url, url); t.is(error.response.headers.connection, 'close'); @@ -40,6 +40,7 @@ test('catches dns errors', async t => { t.regex(error.message, /ENOTFOUND|EAI_AGAIN/); t.is((error.options.url as URL).host, 'doesntexist'); t.is(error.options.method, 'GET'); + t.is(error.code, 'ENOTFOUND'); }); test('`options.body` form error message', async t => { @@ -132,7 +133,8 @@ test('`http.request` error', async t => { } }), { instanceOf: RequestError, - message: 'The header content contains invalid characters' + message: 'The header content contains invalid characters', + code: 'ERR_GOT_REQUEST_ERROR' }); }); @@ -236,7 +238,8 @@ test('promise does not hang on timeout on HTTP error', withServer, async (t, ser request: 100 } }), { - instanceOf: TimeoutError + instanceOf: TimeoutError, + code: 'ETIMEDOUT' }); }); diff --git a/test/http.ts b/test/http.ts index ec0089f66..072f183f6 100644 --- a/test/http.ts +++ b/test/http.ts @@ -93,7 +93,8 @@ test('doesn\'t throw if `options.throwHttpErrors` is false', withServer, async ( test('invalid protocol throws', async t => { await t.throwsAsync(got('c:/nope.com').json(), { instanceOf: RequestError, - message: 'Unsupported protocol: c:' + message: 'Unsupported protocol: c:', + code: 'ERR_UNSUPPORTED_PROTOCOL' }); }); @@ -226,7 +227,8 @@ test('throws an error if the server aborted the request', withServer, async (t, }); const error = await t.throwsAsync(got(''), { - message: 'The server aborted pending request' + message: 'The server aborted pending request', + code: 'ECONNRESET' }); t.truthy(error.response.retryCount); diff --git a/test/post.ts b/test/post.ts index 4ecc00295..020c287fe 100644 --- a/test/post.ts +++ b/test/post.ts @@ -334,7 +334,8 @@ test('throws on upload error', withServer, async (t, server, got) => { } })), { instanceOf: UploadError, - message + message, + code: 'ERR_UPLOAD' }); }); diff --git a/test/promise.ts b/test/promise.ts index 6903007da..f613258ad 100644 --- a/test/promise.ts +++ b/test/promise.ts @@ -71,8 +71,14 @@ test('promise.json() can be called before a file stream body is open', withServe const promise = got({body}); const checks = [ - t.throwsAsync(promise, {instanceOf: CancelError}), - t.throwsAsync(promise.json(), {instanceOf: CancelError}) + t.throwsAsync(promise, { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }), + t.throwsAsync(promise.json(), { + instanceOf: CancelError, + code: 'ERR_CANCELED' + }) ]; promise.cancel(); diff --git a/test/redirects.ts b/test/redirects.ts index d31f7245b..6a989affa 100644 --- a/test/redirects.ts +++ b/test/redirects.ts @@ -84,6 +84,7 @@ test('throws on endless redirects - default behavior', withServer, async (t, ser const error = await t.throwsAsync(got(''), {message: 'Redirected 10 times. Aborting.'}); t.deepEqual(error.response.redirectUrls.map(x => String(x)), Array.from({length: 10}).fill(`${server.url}/`)); + t.is(error.code, 'ERR_TOO_MANY_REDIRECTS'); }); test('custom `maxRedirects` option', withServer, async (t, server, got) => { @@ -97,6 +98,7 @@ test('custom `maxRedirects` option', withServer, async (t, server, got) => { const error = await t.throwsAsync(got('', {maxRedirects: 5}), {message: 'Redirected 5 times. Aborting.'}); t.deepEqual(error.response.redirectUrls.map(x => String(x)), Array.from({length: 5}).fill(`${server.url}/`)); + t.is(error.code, 'ERR_TOO_MANY_REDIRECTS'); }); test('searchParams are not breaking redirects', withServer, async (t, server, got) => { @@ -123,7 +125,8 @@ test('redirects GET and HEAD requests', withServer, async (t, server, got) => { }); await t.throwsAsync(got.get(''), { - instanceOf: MaxRedirectsError + instanceOf: MaxRedirectsError, + code: 'ERR_TOO_MANY_REDIRECTS' }); }); @@ -136,7 +139,8 @@ test('redirects POST requests', withServer, async (t, server, got) => { }); await t.throwsAsync(got.post({body: 'wow'}), { - instanceOf: MaxRedirectsError + instanceOf: MaxRedirectsError, + code: 'ERR_TOO_MANY_REDIRECTS' }); }); diff --git a/test/response-parse.ts b/test/response-parse.ts index 270fa17b1..0e2f75b9a 100644 --- a/test/response-parse.ts +++ b/test/response-parse.ts @@ -98,6 +98,7 @@ test('wraps parsing errors', withServer, async (t, server, got) => { const error = await t.throwsAsync(got({responseType: 'json'}), {instanceOf: ParseError}); t.true(error.message.includes((error.options.url as URL).hostname)); t.is((error.options.url as URL).pathname, '/'); + t.is(error.code, 'ERR_BODY_PARSE_FAILURE'); }); test('parses non-200 responses', withServer, async (t, server, got) => { @@ -134,6 +135,7 @@ test('parse errors have `response` property', withServer, async (t, server, got) t.is(error.response.statusCode, 200); t.is(error.response.body, '/'); + t.is(error.code, 'ERR_BODY_PARSE_FAILURE'); }); test('sets correct headers', withServer, async (t, server, got) => { @@ -184,7 +186,8 @@ test('shortcuts throw ParseErrors', withServer, async (t, server, got) => { await t.throwsAsync(got('').json(), { instanceOf: ParseError, - message: /^Unexpected token o in JSON at position 1 in/ + message: /^Unexpected token o in JSON at position 1 in/, + code: 'ERR_BODY_PARSE_FAILURE' }); }); diff --git a/test/retry.ts b/test/retry.ts index 50fde6609..aeb4e416a 100644 --- a/test/retry.ts +++ b/test/retry.ts @@ -188,7 +188,7 @@ test('custom error codes', async t => { }, retry: { calculateDelay: ({error}) => { - t.is(error.code! as typeof errorCode, errorCode); + t.is(error.code, errorCode); return 0; }, methods: [