From 4b7208a367353e6282e868f041a572967087f1c5 Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Mon, 26 Aug 2024 04:33:23 +0200 Subject: [PATCH] make json-rpc server id handling spec-compliant (#807) * make json-rpc server id handling spec-compliant * await response, remove error code * add string id tests for json rpc server --- packages/chopsticks/src/server.ts | 34 +++++++++++++++++++++---------- packages/e2e/src/http.test.ts | 20 +++++++++++++++++- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/chopsticks/src/server.ts b/packages/chopsticks/src/server.ts index f57e49bc..8dcda116 100644 --- a/packages/chopsticks/src/server.ts +++ b/packages/chopsticks/src/server.ts @@ -9,7 +9,7 @@ const httpLogger = defaultLogger.child({ name: 'http' }) const wsLogger = defaultLogger.child({ name: 'ws' }) const singleRequest = z.object({ - id: z.number(), + id: z.optional(z.union([z.number().int(), z.string(), z.null()])), jsonrpc: z.literal('2.0'), method: z.string(), params: z.array(z.any()).default([]), @@ -91,6 +91,21 @@ export const createServer = async (handler: Handler, port: number) => { }, } + const safeHandleRequest = async (request: z.infer) => { + try { + const result = await handler(request, emptySubscriptionManager) + return request.id === undefined ? undefined : { id: request.id, jsonrpc: '2.0', result } + } catch (err: any) { + return { + jsonrpc: '2.0', + id: request.id, + error: { + message: err.message, + }, + } + } + } + const server = http.createServer(async (req, res) => { if (req.method === 'OPTIONS') { return respond(res) @@ -112,24 +127,21 @@ export const createServer = async (handler: Handler, port: number) => { let response: any if (Array.isArray(parsed.data)) { - response = await Promise.all( - parsed.data.map((req) => { - const result = handler(req, emptySubscriptionManager) - return { id: req.id, jsonrpc: '2.0', result } - }), - ) + response = await Promise.all(parsed.data.map(safeHandleRequest)) + response = response.filter((r) => r !== undefined) } else { - const result = await handler(parsed.data, emptySubscriptionManager) - response = { id: parsed.data.id, jsonrpc: '2.0', result } + response = await safeHandleRequest(parsed.data) } - respond(res, JSON.stringify(response)) + if (response !== undefined) { + respond(res, JSON.stringify(response)) + } } catch (err: any) { respond( res, JSON.stringify({ jsonrpc: '2.0', - id: 1, + id: null, error: { message: err.message, }, diff --git a/packages/e2e/src/http.test.ts b/packages/e2e/src/http.test.ts index ac4c0e2a..263d108e 100644 --- a/packages/e2e/src/http.test.ts +++ b/packages/e2e/src/http.test.ts @@ -106,7 +106,7 @@ describe('http.server', () => { "error": { "message": "Only POST method is supported", }, - "id": 1, + "id": null, "jsonrpc": "2.0", } `, @@ -115,5 +115,23 @@ describe('http.server', () => { }, ).end(body) } + + { + // Accepts string ids + const id = 'lorem ipsum dolor sit amet' + const res = await fetch(`http://localhost:${port}`, { + method: 'POST', + body: JSON.stringify({ id, jsonrpc: '2.0', method: 'chain_getBlockHash', params: [] }), + }) + expect(await res.json()).toMatchInlineSnapshot( + ` + { + "id": "${id}", + "jsonrpc": "2.0", + "result": "0x0df086f32a9c3399f7fa158d3d77a1790830bd309134c5853718141c969299c7", + } + `, + ) + } }) })