Skip to content

Commit

Permalink
Add Response#headers to errors thrown from the HTTP transport (#3564)
Browse files Browse the repository at this point in the history
* Add `Response#headers` to errors thrown from the HTTP transport

* Do a MINOR patch instead, since this is a new feature
  • Loading branch information
steveluscher authored Nov 15, 2024
1 parent 7eb8c56 commit 5af7f20
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .changeset/silent-wolves-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@solana/rpc-transport-http': minor
'@solana/errors': minor
---

When the HTTP transport throws an error, you can now access the response headers through `e.context.headers`. This can be useful, for instance, if the HTTP error is a 429 Rate Limit error, and the response contains a `Retry-After` header.

```ts
try {
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
} catch (e) {
if (isSolanaError(e, SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR)) {
if (e.context.code === 429 /* rate limit error */) {
const retryAfterHeaderValue = e.context.headers.get('Retry-After');
if (retryAfterHeaderValue != null) {
// ...
}
}
}
}
```
1 change: 1 addition & 0 deletions packages/errors/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ export type SolanaErrorContext = DefaultUnspecifiedErrorContextToUndefined<
value: bigint;
};
[SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR]: {
headers: Headers;
message: string;
statusCode: number;
};
Expand Down
37 changes: 37 additions & 0 deletions packages/rpc-transport-http/src/__tests__/http-transport-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ describe('createHttpTransport', () => {
});
});
describe('when the endpoint returns a non-200 status code', () => {
let expectedHeaders: Headers;
beforeEach(() => {
expectedHeaders = new Headers([['Sekrit-Response-Header', 'doNotLog']]);
fetchSpy.mockResolvedValue({
headers: expectedHeaders,
ok: false,
status: 404,
statusText: 'We looked everywhere',
Expand All @@ -31,11 +34,45 @@ describe('createHttpTransport', () => {
const requestPromise = makeHttpRequest({ payload: 123 });
await expect(requestPromise).rejects.toThrow(
new SolanaError(SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR, {
headers: expectedHeaders,
message: 'We looked everywhere',
statusCode: 404,
}),
);
});
it('exposes the response headers on the error context', async () => {
expect.assertions(2);
let thrownError!: SolanaError<typeof SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR>;
try {
await makeHttpRequest({ payload: 123 });
} catch (e) {
thrownError = e as SolanaError<typeof SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR>;
}
expect(thrownError).toBeDefined();
expect(thrownError.context.headers).toBe(expectedHeaders);
});
it('does not leak the response header values through the error message', async () => {
expect.assertions(2);
let thrownError!: SolanaError<typeof SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR>;
try {
await makeHttpRequest({ payload: 123 });
} catch (e) {
thrownError = e as SolanaError<typeof SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR>;
}
expect(thrownError).toBeDefined();
expect(thrownError.message).not.toMatch(/doNotLog/);
});
it('the `Headers` on the error context do not leak the header values when stringified', async () => {
expect.assertions(2);
let thrownError!: SolanaError<typeof SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR>;
try {
await makeHttpRequest({ payload: 123 });
} catch (e) {
thrownError = e as SolanaError<typeof SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR>;
}
expect(thrownError).toBeDefined();
expect(`${thrownError.context.headers}`).not.toMatch(/doNotLog/);
});
});
describe('when the transport fatals', () => {
beforeEach(() => {
Expand Down
1 change: 1 addition & 0 deletions packages/rpc-transport-http/src/http-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export function createHttpTransport(config: Config): RpcTransport {
const response = await fetch(url, requestInfo);
if (!response.ok) {
throw new SolanaError(SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR, {
headers: response.headers,
message: response.statusText,
statusCode: response.status,
});
Expand Down

0 comments on commit 5af7f20

Please sign in to comment.