Skip to content

Commit f018f30

Browse files
refactor!: the TxSubmit endpoint no longer adds the stack trace when returning domain errors
BREAKING CHANGE: - stack property of returned errors was removed
1 parent 48eba64 commit f018f30

File tree

5 files changed

+131
-1
lines changed

5 files changed

+131
-1
lines changed

Diff for: packages/core/src/Cardano/util/txSubmissionErrors.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CardanoNodeErrors } from '../../CardanoNode';
2+
import { isProductionEnvironment, stripStackTrace } from '@cardano-sdk/util';
23

34
/**
45
* Tests the provided error for an instanceof match in the TxSubmissionErrors object
@@ -17,10 +18,19 @@ export const asTxSubmissionError = (error: unknown): CardanoNodeErrors.TxSubmiss
1718
if (Array.isArray(error)) {
1819
for (const err of error) {
1920
if (isTxSubmissionError(err)) {
21+
if (isProductionEnvironment()) stripStackTrace(err);
22+
2023
return err;
2124
}
2225
}
2326
return null;
2427
}
25-
return isTxSubmissionError(error) ? error : null;
28+
29+
if (isTxSubmissionError(error)) {
30+
if (isProductionEnvironment()) stripStackTrace(error);
31+
32+
return error;
33+
}
34+
35+
return null;
2636
};

Diff for: packages/util/src/environment.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Node.js environment configurations.
3+
*/
4+
export enum Environment {
5+
/**
6+
* Production environment.
7+
*
8+
* Node.js assumes it's always running in a development environment. You can signal Node.js that you are running
9+
* in production by setting the NODE_ENV=production environment variable.
10+
*/
11+
Production = 'production',
12+
13+
/**
14+
* Development environment.
15+
*/
16+
Development = 'development'
17+
}
18+
19+
export const isProductionEnvironment = (): boolean => process.env.NODE_ENV === Environment.Production;
20+
21+
export const getEnvironmentConfiguration = (): Environment =>
22+
isProductionEnvironment() ? Environment.Production : Environment.Development;

Diff for: packages/util/src/errors.ts

+29
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ interface ErrorLike {
55
stack: string;
66
}
77

8+
interface WithInnerError {
9+
innerError: string | Error;
10+
}
11+
12+
/**
13+
* Gets whether the given error has an innerError.
14+
*
15+
* @param error The error to be checked for.
16+
*/
17+
const isWithInnerError = (error: unknown): error is WithInnerError =>
18+
error !== null && typeof error === 'object' && 'innerError' in (error as never);
19+
820
/**
921
* This type check works as an "error instanceof Error" check, but it let pass also those objects
1022
* which implements the Error interface without inheriting from the same base class
@@ -22,6 +34,23 @@ const isErrorLike = (error: unknown): error is ErrorLike => {
2234
return typeof message === 'string' && typeof stack === 'string';
2335
};
2436

37+
/**
38+
* Strips the stack trace of all errors and their inner errors recursively.
39+
*
40+
* @param error The error to be stripped of its stack trace.
41+
*/
42+
export const stripStackTrace = (error: unknown) => {
43+
if (!error) return;
44+
45+
if (isErrorLike(error)) {
46+
delete (error as Error).stack;
47+
}
48+
49+
if (isWithInnerError(error)) {
50+
stripStackTrace(error.innerError);
51+
}
52+
};
53+
2554
export class ComposableError<InnerError = unknown> extends CustomError {
2655
private static stackDelimiter = '\n at ';
2756

Diff for: packages/util/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './network';
1212
export * from './logging';
1313
export * from './RunnableModule';
1414
export * from './opaqueTypes';
15+
export * from './environment';

Diff for: packages/util/test/errors.test.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { ComposableError } from '../';
2+
import { stripStackTrace } from '../src';
3+
4+
class TestError<InnerError = unknown> extends ComposableError<InnerError> {
5+
constructor(innerError: InnerError) {
6+
super('Test error', innerError);
7+
}
8+
}
9+
10+
const throwsError = () => {
11+
throw new Error('a');
12+
};
13+
14+
const throwsComposableError = () => {
15+
try {
16+
throwsError();
17+
} catch (error) {
18+
throw new TestError(error);
19+
}
20+
};
21+
22+
describe('stripStackTrace', () => {
23+
it('doesnt throw if given undefined', () => {
24+
const error = undefined;
25+
expect(() => stripStackTrace(error)).not.toThrow();
26+
});
27+
28+
it('doesnt throw if given null', () => {
29+
const error = null;
30+
expect(() => stripStackTrace(error)).not.toThrow();
31+
});
32+
33+
it('doesnt throw if a non Error object', () => {
34+
const error = { a: 'a' };
35+
expect(() => stripStackTrace(error)).not.toThrow();
36+
expect(() => stripStackTrace(10)).not.toThrow();
37+
expect(() => stripStackTrace('some error')).not.toThrow();
38+
});
39+
40+
it('removes the stack field from the Error object', () => {
41+
try {
42+
throwsError();
43+
} catch (error) {
44+
stripStackTrace(error);
45+
expect((error as Error).stack).toBeUndefined();
46+
}
47+
});
48+
49+
it('removes the stack field from the Error object and inner errors', () => {
50+
try {
51+
throwsComposableError();
52+
} catch (error) {
53+
let testError = error as TestError;
54+
let innerError = testError.innerError as Error;
55+
56+
expect(testError.stack).toBeDefined();
57+
expect(innerError.stack).toBeDefined();
58+
59+
stripStackTrace(error);
60+
61+
testError = error as TestError;
62+
innerError = testError.innerError as Error;
63+
64+
expect(testError.stack).toBeUndefined();
65+
expect(innerError.stack).toBeUndefined();
66+
}
67+
});
68+
});

0 commit comments

Comments
 (0)