|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | + |
| 3 | +import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; |
| 4 | +import { ParameterizedContext } from 'koa'; |
| 5 | + |
| 6 | +interface IResponseContext { |
| 7 | + body: { |
| 8 | + jsonrpc: unknown; |
| 9 | + id: unknown; |
| 10 | + result?: unknown; |
| 11 | + error?: { code: unknown; message: unknown }; |
| 12 | + }; |
| 13 | + status: number | undefined; |
| 14 | +} |
| 15 | + |
| 16 | +const VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE = ConfigService.get('VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE'); |
| 17 | + |
| 18 | +const FALLBACK_RESPONSE_BODY = { |
| 19 | + jsonrpc: '2.0', |
| 20 | + id: null, |
| 21 | + error: { code: -32600, message: 'Request body is empty; expected a JSON-RPC 2.0 request' }, |
| 22 | +}; |
| 23 | + |
| 24 | +export const INVALID_METHOD_RESPONSE_BODY = { |
| 25 | + ...FALLBACK_RESPONSE_BODY, |
| 26 | + error: { code: -32600, message: 'Invalid HTTP method: only POST is allowed' }, |
| 27 | +}; |
| 28 | + |
| 29 | +const makeSureBodyExistsAndCanBeChecked = (ctx: IResponseContext) => { |
| 30 | + if (ctx.status === 200) return false; |
| 31 | + |
| 32 | + if (!ctx.body) { |
| 33 | + ctx.status = 400; |
| 34 | + ctx.body = structuredClone(FALLBACK_RESPONSE_BODY); |
| 35 | + return false; |
| 36 | + } |
| 37 | + |
| 38 | + if (Array.isArray(ctx.body)) { |
| 39 | + ctx.status = 200; |
| 40 | + return false; |
| 41 | + } |
| 42 | + |
| 43 | + if (typeof ctx.body !== 'object') { |
| 44 | + ctx.status = 400; |
| 45 | + ctx.body = structuredClone(FALLBACK_RESPONSE_BODY); |
| 46 | + return false; |
| 47 | + } |
| 48 | + if (!ctx.body.jsonrpc) ctx.body.jsonrpc = FALLBACK_RESPONSE_BODY.jsonrpc; |
| 49 | + if (!ctx.body.id) ctx.body.id = FALLBACK_RESPONSE_BODY.id; |
| 50 | + |
| 51 | + return true; |
| 52 | +}; |
| 53 | + |
| 54 | +/** |
| 55 | + * Ensures a JSON-RPC response uses a valid JSON-RPC 2.0 structure. |
| 56 | + * Normalizes missing or invalid fields for both single and batch responses. |
| 57 | + * May update HTTP status depending on VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE. |
| 58 | + * |
| 59 | + * @param {IResponseContext & ParameterizedContext} ctx - Koa context containing status and body. |
| 60 | + */ |
| 61 | +export const jsonRpcComplianceLayer = (ctx: IResponseContext & ParameterizedContext) => { |
| 62 | + if (!makeSureBodyExistsAndCanBeChecked(ctx)) return; |
| 63 | + if (ctx.status === 400) { |
| 64 | + if (!ctx.body.error?.code) ctx.body.error = structuredClone(FALLBACK_RESPONSE_BODY.error); |
| 65 | + if (VALID_JSON_RPC_HTTP_REQUESTS_STATUS_CODE) ctx.status = 200; |
| 66 | + } |
| 67 | +}; |
0 commit comments