From ea59901cded4c7a1d17824b57fcd636ea2089e29 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Mon, 2 Sep 2024 12:32:24 +0100 Subject: [PATCH] Add `fromJson` and `toJson` options to HTTP transport --- .changeset/slow-dragons-add.md | 5 +++ packages/rpc-transport-http/README.md | 8 +++++ .../http-transport-from-json-test.ts | 29 ++++++++++++++++ .../__tests__/http-transport-to-json-test.ts | 33 +++++++++++++++++++ .../rpc-transport-http/src/http-transport.ts | 9 +++-- 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 .changeset/slow-dragons-add.md create mode 100644 packages/rpc-transport-http/src/__tests__/http-transport-from-json-test.ts create mode 100644 packages/rpc-transport-http/src/__tests__/http-transport-to-json-test.ts diff --git a/.changeset/slow-dragons-add.md b/.changeset/slow-dragons-add.md new file mode 100644 index 000000000000..fdf3fe6cef0a --- /dev/null +++ b/.changeset/slow-dragons-add.md @@ -0,0 +1,5 @@ +--- +'@solana/rpc-transport-http': patch +--- + +Add `fromJson` and `toJson` options to the HTTP transport diff --git a/packages/rpc-transport-http/README.md b/packages/rpc-transport-http/README.md index 58aecb1ac999..01797306e94e 100644 --- a/packages/rpc-transport-http/README.md +++ b/packages/rpc-transport-http/README.md @@ -82,6 +82,10 @@ const balances = await Promise.allSettled( ); ``` +##### `fromJson` + +An optional function that takes the response as a JSON string and converts it to a JSON value. The request payload is also provided as a second argument. When not provided, the JSON value will be accessed via the `response.json()` method of the fetch API. + ##### `headers` An object of headers to set on the request. Avoid [forbidden headers](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name). Additionally, the headers `Accept`, `Content-Length`, and `Content-Type` are disallowed. @@ -98,6 +102,10 @@ const transport = createHttpTransport({ }); ``` +##### `toJson` + +An optional function that takes the request payload and converts it to a JSON string. When not provided, `JSON.stringify` will be used. + ##### `url` A string representing the target endpoint. In Node, it must be an absolute URL using the `http` or `https` protocol. diff --git a/packages/rpc-transport-http/src/__tests__/http-transport-from-json-test.ts b/packages/rpc-transport-http/src/__tests__/http-transport-from-json-test.ts new file mode 100644 index 000000000000..7bf97ebbbbba --- /dev/null +++ b/packages/rpc-transport-http/src/__tests__/http-transport-from-json-test.ts @@ -0,0 +1,29 @@ +import { RpcTransport } from '@solana/rpc-spec'; + +describe('createHttpTransport and `fromJson` function', () => { + let fromJson: jest.Mock; + let fetchSpy: jest.SpyInstance; + let makeHttpRequest: RpcTransport; + beforeEach(async () => { + await jest.isolateModulesAsync(async () => { + fromJson = jest.fn(); + fetchSpy = jest.spyOn(globalThis, 'fetch'); + fetchSpy.mockResolvedValue({ ok: true, text: () => '{"ok":true}' }); + const { createHttpTransport } = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + await import('../http-transport'); + makeHttpRequest = createHttpTransport({ fromJson, url: 'http://localhost' }); + }); + }); + it('uses the `fromJson` function to parse the response from a JSON string', async () => { + expect.assertions(1); + await makeHttpRequest({ payload: { foo: 123 } }); + expect(fromJson).toHaveBeenCalledWith('{"ok":true}', { foo: 123 }); + }); + it('returns the value parsed by `fromJson`', async () => { + expect.assertions(1); + fromJson.mockReturnValueOnce({ result: 456 }); + await expect(makeHttpRequest({ payload: { foo: 123 } })).resolves.toEqual({ result: 456 }); + }); +}); diff --git a/packages/rpc-transport-http/src/__tests__/http-transport-to-json-test.ts b/packages/rpc-transport-http/src/__tests__/http-transport-to-json-test.ts new file mode 100644 index 000000000000..b70c3c3cd1b1 --- /dev/null +++ b/packages/rpc-transport-http/src/__tests__/http-transport-to-json-test.ts @@ -0,0 +1,33 @@ +import { RpcTransport } from '@solana/rpc-spec'; + +describe('createHttpTransport and `toJson` function', () => { + let toJson: jest.Mock; + let fetchSpy: jest.SpyInstance; + let makeHttpRequest: RpcTransport; + beforeEach(async () => { + await jest.isolateModulesAsync(async () => { + toJson = jest.fn(value => JSON.stringify(value)); + fetchSpy = jest.spyOn(globalThis, 'fetch'); + fetchSpy.mockResolvedValue({ json: () => ({ ok: true }), ok: true }); + const { createHttpTransport } = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + await import('../http-transport'); + makeHttpRequest = createHttpTransport({ toJson, url: 'http://localhost' }); + }); + }); + it('uses the `toJson` function to transform the payload to a JSON string', () => { + makeHttpRequest({ payload: { foo: 123 } }); + expect(toJson).toHaveBeenCalledWith({ foo: 123 }); + }); + it('uses passes the JSON string to the fetch API', () => { + toJson.mockReturnValueOnce('{"someAugmented":"jsonString"}'); + makeHttpRequest({ payload: { foo: 123 } }); + expect(fetchSpy).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + body: '{"someAugmented":"jsonString"}', + }), + ); + }); +}); diff --git a/packages/rpc-transport-http/src/http-transport.ts b/packages/rpc-transport-http/src/http-transport.ts index 0fb9f0e6996e..d9accae97c38 100644 --- a/packages/rpc-transport-http/src/http-transport.ts +++ b/packages/rpc-transport-http/src/http-transport.ts @@ -10,7 +10,9 @@ import { type Config = Readonly<{ dispatcher_NODE_ONLY?: Dispatcher; + fromJson?: (rawResponse: string, payload: unknown) => RpcResponse; headers?: AllowedHttpRequestHeaders; + toJson?: (payload: unknown) => string; url: string; }>; @@ -32,7 +34,7 @@ export function createHttpTransport(config: Config): RpcTransport { if (__DEV__ && !__NODEJS__ && 'dispatcher_NODE_ONLY' in config) { warnDispatcherWasSuppliedInNonNodeEnvironment(); } - const { headers, url } = config; + const { fromJson, headers, toJson, url } = config; if (__DEV__ && headers) { assertIsAllowedHttpRequestHeaders(headers); } @@ -45,7 +47,7 @@ export function createHttpTransport(config: Config): RpcTransport { payload, signal, }: Parameters[0]): Promise> { - const body = JSON.stringify(payload); + const body = toJson ? toJson(payload) : JSON.stringify(payload); const requestInfo = { ...dispatcherConfig, body, @@ -66,6 +68,9 @@ export function createHttpTransport(config: Config): RpcTransport { statusCode: response.status, }); } + if (fromJson) { + return fromJson(await response.text(), payload) as TResponse; + } return await response.json(); }; }