Commit 24f91e80 authored by Robert Knight's avatar Robert Knight

Include requested URL in FetchError properties

This can enable more helpful error formatting by downstream consumers.
parent 08a2c286
...@@ -9,13 +9,15 @@ ...@@ -9,13 +9,15 @@
*/ */
export class FetchError extends Error { export class FetchError extends Error {
/** /**
* @param {string} url - The URL that was requested. This may be different
* than the final URL of the response if a redirect happened.
* @param {Response|null} response - The response to the `fetch` request or * @param {Response|null} response - The response to the `fetch` request or
* `null` if the fetch failed * `null` if the fetch failed
* @param {string} [reason] - Additional details about the error. This might * @param {string} [reason] - Additional details about the error. This might
* include context of the network request or a server-provided error in * include context of the network request or a server-provided error in
* the response. * the response.
*/ */
constructor(response, reason = '') { constructor(url, response, reason = '') {
let message = 'Network request failed'; let message = 'Network request failed';
if (response) { if (response) {
message += ` (${response.status})`; message += ` (${response.status})`;
...@@ -25,6 +27,7 @@ export class FetchError extends Error { ...@@ -25,6 +27,7 @@ export class FetchError extends Error {
} }
super(message); super(message);
this.url = url;
this.response = response; this.response = response;
this.reason = reason; this.reason = reason;
} }
...@@ -49,7 +52,7 @@ export async function fetchJSON(url, init) { ...@@ -49,7 +52,7 @@ export async function fetchJSON(url, init) {
// If the request fails for any reason, wrap the result in a `FetchError`. // If the request fails for any reason, wrap the result in a `FetchError`.
// Different browsers use different error messages for `fetch` failures, so // Different browsers use different error messages for `fetch` failures, so
// wrapping the error allows downstream clients to handle this uniformly. // wrapping the error allows downstream clients to handle this uniformly.
throw new FetchError(null, err.message); throw new FetchError(url, null, err.message);
} }
if (response.status === 204 /* No Content */) { if (response.status === 204 /* No Content */) {
...@@ -62,14 +65,14 @@ export async function fetchJSON(url, init) { ...@@ -62,14 +65,14 @@ export async function fetchJSON(url, init) {
try { try {
data = await response.json(); data = await response.json();
} catch (err) { } catch (err) {
throw new FetchError(response, 'Failed to parse response'); throw new FetchError(url, response, 'Failed to parse response');
} }
// If the HTTP status indicates failure, attempt to extract a server-provided // If the HTTP status indicates failure, attempt to extract a server-provided
// reason from the response, assuming certain conventions for the formatting // reason from the response, assuming certain conventions for the formatting
// of error responses. // of error responses.
if (!response.ok) { if (!response.ok) {
throw new FetchError(response, data?.reason); throw new FetchError(url, response, data?.reason);
} }
return data; return data;
......
...@@ -37,6 +37,8 @@ describe('sidebar/util/fetch', () => { ...@@ -37,6 +37,8 @@ describe('sidebar/util/fetch', () => {
} }
assert.instanceOf(err, FetchError); assert.instanceOf(err, FetchError);
assert.equal(err.url, 'https://example.com');
assert.equal(err.response, null);
assert.include(err.message, 'Network request failed: Fetch failed'); assert.include(err.message, 'Network request failed: Fetch failed');
}); });
...@@ -55,6 +57,8 @@ describe('sidebar/util/fetch', () => { ...@@ -55,6 +57,8 @@ describe('sidebar/util/fetch', () => {
err = e; err = e;
} }
assert.instanceOf(err, FetchError); assert.instanceOf(err, FetchError);
assert.equal(err.url, 'https://example.com');
assert.equal(err.response, fakeResponse);
assert.equal( assert.equal(
err.message, err.message,
'Network request failed (200): Failed to parse response' 'Network request failed (200): Failed to parse response'
...@@ -71,6 +75,8 @@ describe('sidebar/util/fetch', () => { ...@@ -71,6 +75,8 @@ describe('sidebar/util/fetch', () => {
err = e; err = e;
} }
assert.instanceOf(err, FetchError); assert.instanceOf(err, FetchError);
assert.equal(err.url, 'https://example.com');
assert.equal(err.response, fakeResponse);
assert.equal(err.message, 'Network request failed (400): server error'); assert.equal(err.message, 'Network request failed (400): server error');
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment