Skip to content

Commit b61041a

Browse files
authored
chore: Make error handlers accept unknown and clean up type assertions (#155)
1 parent 2bc64df commit b61041a

21 files changed

+165
-140
lines changed

config/configFile.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
} from '../constants/config';
1212
import { getOrderedConfig } from './configUtils';
1313
import { CLIConfig_NEW } from '../types/Config';
14-
import { BaseError } from '../types/Error';
1514
import { i18n } from '../utils/lang';
1615

1716
const i18nKey = 'config.configFile';
@@ -49,7 +48,7 @@ export function readConfigFile(configPath: string): string {
4948
source = fs.readFileSync(configPath).toString();
5049
} catch (err) {
5150
logger.debug(i18n(`${i18nKey}.errorReading`, { configPath }));
52-
throwFileSystemError(err as BaseError, {
51+
throwFileSystemError(err, {
5352
filepath: configPath,
5453
read: true,
5554
});
@@ -67,7 +66,7 @@ export function parseConfig(configSource: string): CLIConfig_NEW {
6766
try {
6867
parsed = yaml.load(configSource) as CLIConfig_NEW;
6968
} catch (err) {
70-
throwErrorWithMessage(`${i18nKey}.errors.parsing`, {}, err as BaseError);
69+
throwErrorWithMessage(`${i18nKey}.errors.parsing`, {}, err);
7170
}
7271

7372
return parsed;
@@ -104,7 +103,7 @@ export function writeConfigToFile(config: CLIConfig_NEW): void {
104103
JSON.parse(JSON.stringify(getOrderedConfig(config), null, 2))
105104
);
106105
} catch (err) {
107-
throwError(err as BaseError);
106+
throwError(err);
108107
}
109108
const configPath = getConfigFilePath();
110109

@@ -113,7 +112,7 @@ export function writeConfigToFile(config: CLIConfig_NEW): void {
113112
fs.writeFileSync(configPath, source);
114113
logger.debug(i18n(`${i18nKey}.writeSuccess`, { configPath }));
115114
} catch (err) {
116-
throwFileSystemError(err as BaseError, {
115+
throwFileSystemError(err, {
117116
filepath: configPath,
118117
write: true,
119118
});

errors/__tests__/apiErrors.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AxiosError } from 'axios';
1+
import { AxiosError, AxiosHeaders } from 'axios';
22
import {
33
isMissingScopeError,
44
isGatingError,
@@ -10,23 +10,28 @@ import {
1010
isSpecifiedError,
1111
} from '../apiErrors';
1212
import { BaseError } from '../../types/Error';
13+
import { HubSpotAuthError } from '../../models/HubSpotAuthError';
1314

1415
export const newError = (overrides = {}): BaseError => {
1516
return {
1617
name: 'Error',
17-
message: 'An error ocurred',
18+
message: 'An error occurred',
1819
status: 200,
1920
errors: [],
2021
...overrides,
2122
};
2223
};
2324

24-
export const newAxiosError = (overrides = {}): AxiosError => {
25-
return {
26-
...newError(),
27-
isAxiosError: true,
28-
name: 'AxiosError',
29-
response: {
25+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26+
export const newAxiosError = (overrides: any = {}): AxiosError => {
27+
return new AxiosError(
28+
'',
29+
overrides.code || '',
30+
{ headers: new AxiosHeaders(), ...overrides.config },
31+
{
32+
...overrides.request,
33+
},
34+
{
3035
request: {
3136
href: 'http://example.com/',
3237
method: 'GET',
@@ -35,11 +40,10 @@ export const newAxiosError = (overrides = {}): AxiosError => {
3540
headers: {},
3641
status: 200,
3742
statusText: '',
38-
// @ts-expect-error don't need to test headers
3943
config: {},
40-
},
41-
...overrides,
42-
};
44+
...overrides.response,
45+
}
46+
);
4347
};
4448

4549
describe('errors/apiErrors', () => {
@@ -84,7 +88,7 @@ describe('errors/apiErrors', () => {
8488
data: { category: 'BANNED', subCategory: 'USER_ACCESS_NOT_ALLOWED' },
8589
},
8690
});
87-
const error1 = newError({ cause: axiosError });
91+
const error1 = new Error('', { cause: axiosError });
8892
expect(
8993
isSpecifiedError(error1, {
9094
statusCode: 403,
@@ -133,12 +137,10 @@ describe('errors/apiErrors', () => {
133137
describe('isApiUploadValidationError()', () => {
134138
it('returns true for api upload validation errors', () => {
135139
const error1 = newAxiosError({
136-
status: 400,
137-
response: { data: { message: 'upload validation error' } },
140+
response: { data: { message: 'upload validation error' }, status: 400 },
138141
});
139142
const error2 = newAxiosError({
140-
status: 400,
141-
response: { data: { errors: [] } },
143+
response: { data: { errors: [] }, status: 400 },
142144
});
143145
expect(isApiUploadValidationError(error1)).toBe(true);
144146
expect(isApiUploadValidationError(error2)).toBe(true);
@@ -156,7 +158,16 @@ describe('errors/apiErrors', () => {
156158

157159
describe('isSpecifiedHubSpotAuthError()', () => {
158160
it('returns true for matching HubSpot auth errors', () => {
159-
const error1 = newError({ name: 'HubSpotAuthError', status: 123 });
161+
const error1 = new HubSpotAuthError('', {
162+
cause: new AxiosError(
163+
'',
164+
'',
165+
{ headers: new AxiosHeaders() },
166+
{},
167+
// @ts-expect-error Not going to mock the whole thing
168+
{ status: 123 }
169+
),
170+
});
160171
expect(isSpecifiedHubSpotAuthError(error1, { status: 123 })).toBe(true);
161172
});
162173

errors/__tests__/standardErrors.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,52 @@ import {
55
throwAuthErrorWithMessage,
66
throwError,
77
} from '../standardErrors';
8-
import { BaseError } from '../../types/Error';
98
import { HubSpotAuthError } from '../../models/HubSpotAuthError';
109
import { AxiosError } from 'axios';
1110

12-
export const newError = (overrides = {}): BaseError => {
13-
return {
14-
name: 'Error',
15-
message: 'An error ocurred',
11+
class FakeSystemError extends Error {
12+
private code?: string | null;
13+
private syscall?: string | null;
14+
private errors?: string[] | null;
15+
private errno?: number | null;
16+
17+
constructor(
18+
message: string,
19+
options?: ErrorOptions,
20+
errno?: number | null,
21+
code?: string | null,
22+
syscall?: string | null,
23+
errors?: string[] | null
24+
) {
25+
super(message, options);
26+
this.code = code;
27+
this.syscall = syscall;
28+
this.errno = errno;
29+
this.errors = errors;
30+
}
31+
}
32+
33+
export const newError = (overrides?: {
34+
errno?: number | null;
35+
code?: string | null;
36+
syscall?: string | null;
37+
errors?: string[] | null;
38+
}): FakeSystemError => {
39+
const defaults = {
1640
errno: 1,
1741
code: 'error_code',
1842
syscall: 'error_syscall',
1943
errors: [],
20-
...overrides,
2144
};
45+
const { errno, syscall, code, errors } = { ...defaults, ...overrides };
46+
return new FakeSystemError(
47+
'An error ocurred',
48+
{},
49+
errno,
50+
code,
51+
syscall,
52+
errors
53+
);
2254
};
2355

2456
describe('errors/standardErrors', () => {
@@ -40,7 +72,7 @@ describe('errors/standardErrors', () => {
4072

4173
describe('isFatalError()', () => {
4274
it('returns true for fatal errors', () => {
43-
const cause = newError() as AxiosError<{
75+
const cause = newError() as unknown as AxiosError<{
4476
category: string;
4577
subcategory: string;
4678
}>;
@@ -56,18 +88,16 @@ describe('errors/standardErrors', () => {
5688

5789
describe('throwErrorWithMessage()', () => {
5890
it('throws error with message', () => {
59-
const error = newError();
6091
expect(() =>
61-
throwErrorWithMessage('errors.generic', {}, error)
92+
throwErrorWithMessage('errors.generic', {}, new AxiosError())
6293
).toThrow();
6394
});
6495
});
6596

6697
describe('throwAuthErrorWithMessage()', () => {
6798
it('throws auth error with message', () => {
68-
const error = newError() as AxiosError;
6999
expect(() =>
70-
throwAuthErrorWithMessage('errors.generic', {}, error)
100+
throwAuthErrorWithMessage('errors.generic', {}, new AxiosError())
71101
).toThrow();
72102
});
73103
});

errors/apiErrors.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
import { AxiosError } from 'axios';
2-
import {
3-
GenericError,
4-
AxiosErrorContext,
5-
BaseError,
6-
ValidationError,
7-
} from '../types/Error';
1+
import { AxiosError, isAxiosError } from 'axios';
2+
import { AxiosErrorContext, BaseError, ValidationError } from '../types/Error';
83
import { HTTP_METHOD_VERBS, HTTP_METHOD_PREPOSITIONS } from '../constants/api';
94
import { i18n } from '../utils/lang';
105
import { throwError } from './standardErrors';
@@ -14,7 +9,7 @@ import { HttpMethod } from '../types/Api';
149
const i18nKey = 'errors.apiErrors';
1510

1611
export function isSpecifiedError(
17-
err: Error | AxiosError,
12+
err: unknown,
1813
{
1914
statusCode,
2015
category,
@@ -29,6 +24,10 @@ export function isSpecifiedError(
2924
code?: string;
3025
}
3126
): boolean {
27+
if (!(err instanceof Error)) {
28+
return false;
29+
}
30+
3231
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3332
const error = (err && (err.cause as AxiosError<any>)) || err;
3433
const statusCodeErr = !statusCode || error.response?.status === statusCode;
@@ -49,41 +48,40 @@ export function isSpecifiedError(
4948
);
5049
}
5150

52-
export function isMissingScopeError(err: Error | AxiosError): boolean {
51+
export function isMissingScopeError(err: unknown): boolean {
5352
return isSpecifiedError(err, { statusCode: 403, category: 'MISSING_SCOPES' });
5453
}
5554

56-
export function isGatingError(err: Error | AxiosError): boolean {
55+
export function isGatingError(err: unknown): boolean {
5756
return isSpecifiedError(err, { statusCode: 403, category: 'GATED' });
5857
}
5958

60-
export function isTimeoutError(err: Error | AxiosError): boolean {
59+
export function isTimeoutError(err: unknown): boolean {
6160
return isSpecifiedError(err, { code: 'ETIMEDOUT' });
6261
}
6362

6463
// eslint-disable-next-line @typescript-eslint/no-explicit-any
65-
export function isApiUploadValidationError(err: AxiosError<any>): boolean {
64+
export function isApiUploadValidationError(err: unknown): boolean {
6665
return (
67-
err.isAxiosError &&
66+
isAxiosError(err) &&
6867
(err.status === 400 || err.response?.status === 400) &&
6968
!!err.response &&
7069
!!(err.response?.data?.message || !!err.response?.data?.errors)
7170
);
7271
}
7372

7473
export function isSpecifiedHubSpotAuthError(
75-
err: GenericError,
74+
err: unknown,
7675
{ status, category, subCategory }: Partial<HubSpotAuthError>
77-
): boolean {
76+
): err is HubSpotAuthError {
77+
if (!err || !(err instanceof HubSpotAuthError)) {
78+
return false;
79+
}
80+
7881
const statusCodeErr = !status || err.status === status;
7982
const categoryErr = !category || err.category === category;
8083
const subCategoryErr = !subCategory || err.subCategory === subCategory;
81-
return Boolean(
82-
err.name === 'HubSpotAuthError' &&
83-
statusCodeErr &&
84-
categoryErr &&
85-
subCategoryErr
86-
);
84+
return Boolean(statusCodeErr && categoryErr && subCategoryErr);
8785
}
8886

8987
export function parseValidationErrors(
@@ -238,10 +236,10 @@ export function getAxiosErrorWithContext(
238236
* @throws
239237
*/
240238
export function throwApiError(
241-
error: AxiosError,
239+
error: unknown,
242240
context: AxiosErrorContext = {}
243241
): never {
244-
if (error.isAxiosError) {
242+
if (isAxiosError(error)) {
245243
throw getAxiosErrorWithContext(error, context);
246244
}
247245
throwError(error);

errors/fileSystemErrors.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { i18n } from '../utils/lang';
22
import { isSystemError } from './standardErrors';
3-
import { BaseError, FileSystemErrorContext } from '../types/Error';
3+
import { FileSystemErrorContext } from '../types/Error';
44

55
const i18nKey = 'errors.fileSystemErrors';
66

77
export function getFileSystemError(
8-
error: BaseError,
8+
error: unknown,
99
context: FileSystemErrorContext
1010
): Error {
1111
let fileAction = '';
@@ -36,7 +36,7 @@ export function getFileSystemError(
3636
* @throws
3737
*/
3838
export function throwFileSystemError(
39-
error: BaseError,
39+
error: unknown,
4040
context: FileSystemErrorContext
4141
) {
4242
throw getFileSystemError(error, context);

0 commit comments

Comments
 (0)