diff --git a/src/client.ts b/src/client.ts index eb7d06e..46956d8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3,6 +3,7 @@ import { EnvironmentContext, getEnvironmentContext, MissingBlobsEnvironmentError import { encodeMetadata, Metadata, METADATA_HEADER_EXTERNAL, METADATA_HEADER_INTERNAL } from './metadata.ts' import { fetchAndRetry } from './retry.ts' import { BlobInput, Fetcher, HTTPMethod } from './types.ts' +import { BlobsInternalError } from './util.ts' export const SIGNED_URL_ACCEPT_HEADER = 'application/json;type=signed-url' @@ -156,7 +157,7 @@ export class Client { }) if (res.status !== 200) { - throw new Error(`Netlify Blobs has generated an internal error: ${res.status} response`) + throw new BlobsInternalError(res) } const { url: signedURL } = await res.json() diff --git a/src/headers.ts b/src/headers.ts new file mode 100644 index 0000000..f7e8526 --- /dev/null +++ b/src/headers.ts @@ -0,0 +1 @@ +export const NF_REQUEST_ID = 'x-nf-request-id' diff --git a/src/main.test.ts b/src/main.test.ts index 3ef81c4..133e6f3 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -8,6 +8,7 @@ import { MockFetch } from '../test/mock_fetch.js' import { base64Encode, streamToString } from '../test/util.js' import { MissingBlobsEnvironmentError } from './environment.js' +import { NF_REQUEST_ID } from './headers.js' import { getDeployStore, getStore, setEnvironmentContext } from './main.js' import { base64Decode } from './util.js' @@ -118,9 +119,10 @@ describe('get', () => { }) test('Throws when the API returns a non-200 status code', async () => { + const mockRequestID = '123456789' const mockStore = new MockFetch().get({ headers: { accept: 'application/json;type=signed-url', authorization: `Bearer ${apiToken}` }, - response: new Response(null, { status: 401 }), + response: new Response(null, { headers: { [NF_REQUEST_ID]: mockRequestID }, status: 401 }), url: `https://api.netlify.com/api/v1/blobs/${siteID}/site:production/${key}`, }) @@ -133,7 +135,7 @@ describe('get', () => { }) expect(async () => await blobs.get(key)).rejects.toThrowError( - `Netlify Blobs has generated an internal error: 401 response`, + `Netlify Blobs has generated an internal error: 401 response (ID: ${mockRequestID})`, ) expect(mockStore.fulfilled).toBeTruthy() }) diff --git a/src/store.ts b/src/store.ts index 2371ada..35450be 100644 --- a/src/store.ts +++ b/src/store.ts @@ -92,7 +92,7 @@ export class Store { const res = await this.client.makeRequest({ key, method: HTTPMethod.DELETE, storeName: this.name }) if (![200, 204, 404].includes(res.status)) { - throw new BlobsInternalError(res.status) + throw new BlobsInternalError(res) } } @@ -116,7 +116,7 @@ export class Store { } if (res.status !== 200) { - throw new BlobsInternalError(res.status) + throw new BlobsInternalError(res) } if (type === undefined || type === 'text') { @@ -139,7 +139,7 @@ export class Store { return res.body } - throw new BlobsInternalError(res.status) + throw new BlobsInternalError(res) } async getMetadata(key: string, { consistency }: { consistency?: ConsistencyMode } = {}) { @@ -150,7 +150,7 @@ export class Store { } if (res.status !== 200 && res.status !== 304) { - throw new BlobsInternalError(res.status) + throw new BlobsInternalError(res) } const etag = res?.headers.get('etag') ?? undefined @@ -221,7 +221,7 @@ export class Store { } if (res.status !== 200 && res.status !== 304) { - throw new BlobsInternalError(res.status) + throw new BlobsInternalError(res) } const responseETag = res?.headers.get('etag') ?? undefined @@ -293,7 +293,7 @@ export class Store { }) if (res.status !== 200) { - throw new BlobsInternalError(res.status) + throw new BlobsInternalError(res) } } @@ -315,7 +315,7 @@ export class Store { }) if (res.status !== 200) { - throw new BlobsInternalError(res.status) + throw new BlobsInternalError(res) } } diff --git a/src/util.ts b/src/util.ts index ddc4d90..8b1742e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,6 +1,14 @@ +import { NF_REQUEST_ID } from './headers.ts' + export class BlobsInternalError extends Error { - constructor(statusCode: number) { - super(`Netlify Blobs has generated an internal error: ${statusCode} response`) + constructor(res: Response) { + let message = `Netlify Blobs has generated an internal error: ${res.status} response` + + if (res.headers.has(NF_REQUEST_ID)) { + message += ` (ID: ${res.headers.get(NF_REQUEST_ID)})` + } + + super(message) this.name = 'BlobsInternalError' }