Skip to content

Commit

Permalink
Add JSON-RPC error validation functions (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
FrederikBolding authored Nov 1, 2022
1 parent 0e252ee commit 6f888c7
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 2 deletions.
70 changes: 70 additions & 0 deletions src/__fixtures__/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,76 @@ export const JSON_RPC_FAILURE_FIXTURES = {
],
};

export const JSON_RPC_ERROR_FIXTURES = {
valid: JSON_RPC_FAILURE_FIXTURES.valid.map((fixture) => fixture.error),
invalid: [
{},
[],
true,
false,
null,
undefined,
1,
'foo',
{
code: {},
message: 'Internal error',
},
{
code: [],
message: 'Internal error',
},
{
code: true,
message: 'Internal error',
},
{
code: false,
message: 'Internal error',
},
{
code: null,
message: 'Internal error',
},
{
code: undefined,
message: 'Internal error',
},
{
code: 'foo',
message: 'Internal error',
},
{
code: -32000,
message: {},
},
{
code: -32000,
message: [],
},
{
code: -32000,
message: true,
},
{
code: -32000,
message: false,
},
{
code: -32000,
message: null,
},
{
code: -32000,
message: undefined,
},
{
code: -32000.5,
message: undefined,
},
],
};

export const JSON_RPC_RESPONSE_FIXTURES = {
valid: [
...JSON_RPC_SUCCESS_FIXTURES.valid,
Expand Down
55 changes: 55 additions & 0 deletions src/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ARRAY_OF_MIXED_SPECIAL_OBJECTS,
COMPLEX_OBJECT,
JSON_FIXTURES,
JSON_RPC_ERROR_FIXTURES,
JSON_RPC_FAILURE_FIXTURES,
JSON_RPC_NOTIFICATION_FIXTURES,
JSON_RPC_PENDING_RESPONSE_FIXTURES,
Expand All @@ -29,6 +30,8 @@ import {
isPendingJsonRpcResponse,
isValidJson,
validateJsonAndGetSize,
isJsonRpcError,
assertIsJsonRpcError,
} from '.';

describe('json', () => {
Expand Down Expand Up @@ -257,6 +260,58 @@ describe('json', () => {
});
});

describe('isJsonRpcError', () => {
it.each(JSON_RPC_ERROR_FIXTURES.valid)(
'returns true for a valid JSON-RPC error',
(error) => {
expect(isJsonRpcError(error)).toBe(true);
},
);

it.each(JSON_RPC_ERROR_FIXTURES.invalid)(
'returns false for an invalid JSON-RPC error',
(error) => {
expect(isJsonRpcError(error)).toBe(false);
},
);
});

describe('assertIsJsonRpcError', () => {
it.each(JSON_RPC_ERROR_FIXTURES.valid)(
'does not throw an error for valid JSON-RPC error',
(error) => {
expect(() => assertIsJsonRpcError(error)).not.toThrow();
},
);

it.each(JSON_RPC_ERROR_FIXTURES.invalid)(
'throws an error for invalid JSON-RPC error',
(error) => {
expect(() => assertIsJsonRpcError(error)).toThrow(
'Not a JSON-RPC error',
);
},
);

it('includes the reason in the error message', () => {
expect(() =>
assertIsJsonRpcError(JSON_RPC_ERROR_FIXTURES.invalid[0]),
).toThrow(
'Not a JSON-RPC error: At path: code -- Expected an integer, but received: undefined.',
);
});

it('includes the value thrown in the message if it is not an error', () => {
jest.spyOn(superstructModule, 'assert').mockImplementation(() => {
throw 'oops';
});

expect(() =>
assertIsJsonRpcError(JSON_RPC_ERROR_FIXTURES.invalid[0]),
).toThrow('Not a JSON-RPC error: oops');
});
});

describe('isPendingJsonRpcResponse', () => {
it.each(JSON_RPC_PENDING_RESPONSE_FIXTURES.valid)(
'returns true for a valid pending JSON-RPC response',
Expand Down
31 changes: 29 additions & 2 deletions src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
assert,
boolean,
Infer,
integer,
is,
lazy,
literal,
Expand Down Expand Up @@ -94,9 +95,9 @@ export const JsonRpcIdStruct = nullable(union([number(), string()]));
export type JsonRpcId = Infer<typeof JsonRpcIdStruct>;

export const JsonRpcErrorStruct = object({
code: number(),
code: integer(),
message: string(),
data: optional(unknown()),
data: optional(JsonStruct),
stack: optional(string()),
});

Expand Down Expand Up @@ -400,6 +401,32 @@ export function assertIsJsonRpcFailure(
}
}

/**
* Type guard to validate whether an object is a valid JSON-RPC error.
*
* @param value - The value object to check.
* @returns Whether the response object is a valid JSON-RPC error.
*/
export function isJsonRpcError(value: unknown): value is JsonRpcError {
return is(value, JsonRpcErrorStruct);
}

/**
* Type assertion to validate whether an object is a valid JSON-RPC error.
*
* @param value - The value object to check.
*/
export function assertIsJsonRpcError(
value: unknown,
): asserts value is JsonRpcError {
try {
assert(value, JsonRpcErrorStruct);
} catch (error) {
const message = isErrorWithMessage(error) ? error.message : error;
throw new Error(`Not a JSON-RPC error: ${message}.`);
}
}

type JsonRpcValidatorOptions = {
permitEmptyString?: boolean;
permitFractions?: boolean;
Expand Down

0 comments on commit 6f888c7

Please sign in to comment.