Skip to content

Commit

Permalink
feat(streams): Add shouldPreserveMessage param to serializeError
Browse files Browse the repository at this point in the history
  • Loading branch information
rekmarks committed Oct 8, 2024
1 parent 70cc036 commit cb3a6b4
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 49 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 92.59,
branches: 92.77,
functions: 94.73,
lines: 92.64,
statements: 92.64,
Expand Down
100 changes: 59 additions & 41 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { dataHasCause, getMessageFromCode, serializeError } from './utils';
const rpcCodes = errorCodes.rpc;

describe('serializeError', () => {
it('handles invalid error: non-object', () => {
it('serializes invalid error: non-object', () => {
const result = serializeError(invalidError0);
expect(result).toStrictEqual({
code: rpcCodes.internal,
Expand All @@ -30,7 +30,7 @@ describe('serializeError', () => {
});
});

it('handles invalid error: null', () => {
it('serializes invalid error: null', () => {
const result = serializeError(invalidError5);
expect(result).toStrictEqual({
code: rpcCodes.internal,
Expand All @@ -39,7 +39,7 @@ describe('serializeError', () => {
});
});

it('handles invalid error: undefined', () => {
it('serializes invalid error: undefined', () => {
const result = serializeError(invalidError6);
expect(result).toStrictEqual({
code: rpcCodes.internal,
Expand All @@ -48,7 +48,7 @@ describe('serializeError', () => {
});
});

it('handles invalid error: array', () => {
it('serializes invalid error: array', () => {
const result = serializeError(invalidError1);
expect(result).toStrictEqual({
code: rpcCodes.internal,
Expand All @@ -57,7 +57,27 @@ describe('serializeError', () => {
});
});

it('handles invalid error: invalid code', () => {
it('serializes invalid error: array with non-JSON values', () => {
const error = ['foo', Symbol('bar'), { baz: 'qux', symbol: Symbol('') }];
const result = serializeError(error);
expect(result).toStrictEqual({
code: rpcCodes.internal,
message: getMessageFromCode(rpcCodes.internal),
data: {
cause: ['foo', null, { baz: 'qux' }],
},
});

expect(JSON.parse(JSON.stringify(result))).toStrictEqual({
code: rpcCodes.internal,
message: getMessageFromCode(rpcCodes.internal),
data: {
cause: ['foo', null, { baz: 'qux' }],
},
});
});

it('serializes invalid error: invalid code', () => {
const result = serializeError(invalidError2);
expect(result).toStrictEqual({
code: rpcCodes.internal,
Expand All @@ -66,7 +86,7 @@ describe('serializeError', () => {
});
});

it('handles invalid error: valid code, undefined message', () => {
it('serializes invalid error: valid code, undefined message', () => {
const result = serializeError(invalidError3);
expect(result).toStrictEqual({
code: errorCodes.rpc.internal,
Expand All @@ -79,7 +99,7 @@ describe('serializeError', () => {
});
});

it('handles invalid error: non-string message with data', () => {
it('serializes invalid error: non-string message with data', () => {
const result = serializeError(invalidError4);
expect(result).toStrictEqual({
code: rpcCodes.internal,
Expand All @@ -94,7 +114,7 @@ describe('serializeError', () => {
});
});

it('handles invalid error: invalid code with string message', () => {
it('serializes invalid error: invalid code with string message', () => {
const result = serializeError(invalidError7);
expect(result).toStrictEqual({
code: rpcCodes.internal,
Expand All @@ -109,7 +129,7 @@ describe('serializeError', () => {
});
});

it('handles invalid error: invalid code, no message, custom fallback', () => {
it('serializes invalid error: invalid code, no message, custom fallback', () => {
const result = serializeError(invalidError2, {
fallbackError: { code: rpcCodes.methodNotFound, message: 'foo' },
});
Expand All @@ -120,15 +140,15 @@ describe('serializeError', () => {
});
});

it('handles valid error: code and message only', () => {
it('serializes valid error: code and message only', () => {
const result = serializeError(validError0);
expect(result).toStrictEqual({
code: 4001,
message: validError0.message,
});
});

it('handles valid error: code, message, and data', () => {
it('serializes valid error: code, message, and data', () => {
const result = serializeError(validError1);
expect(result).toStrictEqual({
code: 4001,
Expand All @@ -137,23 +157,23 @@ describe('serializeError', () => {
});
});

it('handles valid error: instantiated error', () => {
it('serializes valid error: instantiated error', () => {
const result = serializeError(validError2);
expect(result).toStrictEqual({
code: rpcCodes.parse,
message: getMessageFromCode(rpcCodes.parse),
});
});

it('handles valid error: other instantiated error', () => {
it('serializes valid error: other instantiated error', () => {
const result = serializeError(validError3);
expect(result).toStrictEqual({
code: rpcCodes.parse,
message: dummyMessage,
});
});

it('handles valid error: instantiated error with custom message and data', () => {
it('serializes valid error: instantiated error with custom message and data', () => {
const result = serializeError(validError4);
expect(result).toStrictEqual({
code: rpcCodes.parse,
Expand All @@ -162,7 +182,7 @@ describe('serializeError', () => {
});
});

it('handles valid error: message and data', () => {
it('serializes valid error: message and data', () => {
const result = serializeError(Object.assign({}, validError1));
expect(result).toStrictEqual({
code: 4001,
Expand All @@ -171,7 +191,7 @@ describe('serializeError', () => {
});
});

it('handles including stack: no stack present', () => {
it('serializes valid error: no stack present', () => {
const result = serializeError(validError1);
expect(result).toStrictEqual({
code: 4001,
Expand All @@ -180,7 +200,7 @@ describe('serializeError', () => {
});
});

it('handles including stack: string stack present', () => {
it('serializes valid error: string stack present', () => {
const result = serializeError(
Object.assign({}, validError1, { stack: 'foo' }),
);
Expand All @@ -192,7 +212,7 @@ describe('serializeError', () => {
});
});

it('handles removing stack', () => {
it('removes the stack with `shouldIncludeStack: false`', () => {
const result = serializeError(
Object.assign({}, validError1, { stack: 'foo' }),
{ shouldIncludeStack: false },
Expand All @@ -204,7 +224,25 @@ describe('serializeError', () => {
});
});

it('handles regular Error()', () => {
it('overwrites the original message with `shouldPreserveMessage: false`', () => {
const error = new Error('foo');
const result = serializeError(error, {
shouldPreserveMessage: false,
fallbackError: validError0,
});
expect(result).toStrictEqual({
code: validError0.code,
message: validError0.message,
data: {
cause: {
message: error.message,
stack: error.stack,
},
},
});
});

it('serializes invalid error: Error', () => {
const error = new Error('foo');
const result = serializeError(error);
expect(result).toStrictEqual({
Expand All @@ -230,7 +268,7 @@ describe('serializeError', () => {
});
});

it('handles JsonRpcError', () => {
it('serializes valid error: JsonRpcError', () => {
const error = rpcErrors.invalidParams();
const result = serializeError(error);
expect(result).toStrictEqual({
Expand All @@ -246,7 +284,7 @@ describe('serializeError', () => {
});
});

it('handles class that has serialize function', () => {
it('serializes error with serialize() method', () => {
class MockClass {
serialize() {
return { code: 1, message: 'foo' };
Expand Down Expand Up @@ -289,26 +327,6 @@ describe('serializeError', () => {
'Must provide fallback error with integer number code and string message.',
);
});

it('handles arrays passed as error', () => {
const error = ['foo', Symbol('bar'), { baz: 'qux', symbol: Symbol('') }];
const result = serializeError(error);
expect(result).toStrictEqual({
code: rpcCodes.internal,
message: getMessageFromCode(rpcCodes.internal),
data: {
cause: ['foo', null, { baz: 'qux' }],
},
});

expect(JSON.parse(JSON.stringify(result))).toStrictEqual({
code: rpcCodes.internal,
message: getMessageFromCode(rpcCodes.internal),
data: {
cause: ['foo', null, { baz: 'qux' }],
},
});
});
});

describe('dataHasCause', () => {
Expand Down
24 changes: 17 additions & 7 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,22 +96,28 @@ export function isValidCode(code: unknown): code is number {
* @param error - The error to serialize.
* @param options - Options bag.
* @param options.fallbackError - The error to return if the given error is
* not compatible. Should be a JSON serializable value.
* not compatible. Should be a JSON-serializable value.
* @param options.shouldIncludeStack - Whether to include the error's stack
* on the returned object.
* @param options.shouldPreserveMessage - Whether to preserve the error's
* message if the fallback error is used.
* @returns The serialized error.
*/
export function serializeError(
error: unknown,
{ fallbackError = FALLBACK_ERROR, shouldIncludeStack = true } = {},
{
fallbackError = FALLBACK_ERROR,
shouldIncludeStack = true,
shouldPreserveMessage = true,
} = {},
): SerializedJsonRpcError {
if (!isJsonRpcError(fallbackError)) {
throw new Error(
'Must provide fallback error with integer number code and string message.',
);
}

const serialized = buildError(error, fallbackError);
const serialized = buildError(error, fallbackError, shouldPreserveMessage);

if (!shouldIncludeStack) {
delete serialized.stack;
Expand All @@ -121,15 +127,18 @@ export function serializeError(
}

/**
* Construct a JSON-serializable object given an error and a JSON serializable `fallbackError`
* Construct a JSON-serializable object given an error and a JSON-serializable `fallbackError`
*
* @param error - The error in question.
* @param fallbackError - A JSON serializable fallback error.
* @returns A JSON serializable error object.
* @param fallbackError - A JSON-serializable fallback error.
* @param shouldPreserveMessage - Whether to preserve the error's message if the fallback
* error is used.
* @returns A JSON-serializable error object.
*/
function buildError(
error: unknown,
fallbackError: SerializedJsonRpcError,
shouldPreserveMessage: boolean,
): SerializedJsonRpcError {
// If an error specifies a `serialize` function, we call it and return the result.
if (
Expand All @@ -151,7 +160,8 @@ function buildError(
const cause = serializeCause(error);
const fallbackWithCause = {
...fallbackError,
...(originalMessage && { message: originalMessage }),
...(shouldPreserveMessage &&
originalMessage && { message: originalMessage }),
data: { cause },
};

Expand Down

0 comments on commit cb3a6b4

Please sign in to comment.