Skip to content

Commit e927dfd

Browse files
authored
feat: Add the ability to pass additional debug context (#346)
1 parent 82606fc commit e927dfd

File tree

6 files changed

+113
-31
lines changed

6 files changed

+113
-31
lines changed

config/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ There are a handful of standard config utils that anyone working in this library
1313
#### getConfig()
1414

1515
Locates and parses the hubspot config file. This function will automatically find the correct config file using the following criteria:
16+
1617
1. Checks to see if a config was specified via environment variables (see below)
1718
2. If no environment variables are present, looks for a global config file (located at `~/.hscli/config.yml`)
1819
3. If no global config file is found, looks up the file tree for a local config file.
1920
4. If no local config file is found, throws an error
2021

21-
2222
##### Custom config location environment variables
23+
2324
- `HUBSPOT_CONFIG_PATH` - specify a file path to a specific config file
2425
- `USE_ENVIRONMENT_HUBSPOT_CONFIG` - load config account from environment variables. The following environment variables are supported:
2526
- `HUBSPOT_PERSONAL_ACCESS_KEY`

config/__tests__/utils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ describe('config/utils', () => {
547547
{
548548
...PAK_ACCOUNT,
549549
someUndefinedField: undefined,
550-
} as any,
550+
},
551551
],
552552
};
553553
const formattedConfig = formatConfigForWrite(configWithUndefined);

errors/index.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
import { HubSpotHttpError } from '../models/HubSpotHttpError';
1+
import {
2+
HubSpotHttpError,
3+
HubSpotHttpErrorName,
4+
} from '../models/HubSpotHttpError';
25
import { BaseError } from '../types/Error';
3-
import { FileSystemError } from '../models/FileSystemError';
6+
import {
7+
FilerSystemErrorName,
8+
FileSystemError,
9+
} from '../models/FileSystemError';
410
import { HubSpotConfigError } from '../models/HubSpotConfigError';
511

612
export function isSpecifiedError(
@@ -60,7 +66,7 @@ export function isAuthError(err: unknown): err is HubSpotHttpError {
6066
);
6167
}
6268

63-
export function isValidationError(err: unknown): boolean {
69+
export function isValidationError(err: unknown): err is HubSpotHttpError {
6470
return (
6571
isHubSpotHttpError(err) &&
6672
isSpecifiedError(err, { statusCode: 400 }) &&
@@ -69,10 +75,12 @@ export function isValidationError(err: unknown): boolean {
6975
}
7076

7177
export function isHubSpotHttpError(error?: unknown): error is HubSpotHttpError {
72-
return !!error && error instanceof HubSpotHttpError;
78+
return (
79+
!!error && error instanceof Error && error.name === HubSpotHttpErrorName
80+
);
7381
}
7482

75-
export function isGithubRateLimitError(err: unknown): boolean {
83+
export function isGithubRateLimitError(err: unknown): err is HubSpotHttpError {
7684
if (!isHubSpotHttpError(err)) {
7785
return false;
7886
}
@@ -96,7 +104,7 @@ export function isSystemError(err: unknown): err is BaseError {
96104
}
97105

98106
export function isFileSystemError(err: unknown): err is FileSystemError {
99-
return err instanceof FileSystemError;
107+
return err instanceof Error && err.name === FilerSystemErrorName;
100108
}
101109

102110
export function isHubSpotConfigError(err: unknown): err is HubSpotConfigError {

models/FileSystemError.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { isSystemError } from '../errors';
44

55
const i18nKey = 'errors.fileSystemErrors';
66

7+
export const FilerSystemErrorName = 'FilerSystemError';
8+
79
export class FileSystemError extends Error {
810
private context: FileSystemErrorContext | undefined;
911

1012
constructor(options?: ErrorOptions, context?: FileSystemErrorContext) {
1113
super('', options);
12-
this.name = 'FileSystemError';
14+
this.name = FilerSystemErrorName;
1315
this.context = context;
1416

1517
if (context) {

models/HubSpotHttpError.ts

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { HttpMethod } from '../types/Api';
44
import { HTTP_METHOD_PREPOSITIONS, HTTP_METHOD_VERBS } from '../constants/api';
55
import { i18n } from '../utils/lang';
66

7+
export const HubSpotHttpErrorName = 'HubSpotHttpError';
8+
79
// eslint-disable-next-line @typescript-eslint/no-explicit-any
810
export class HubSpotHttpError<T = any> extends Error {
911
public status?: number;
@@ -27,7 +29,7 @@ export class HubSpotHttpError<T = any> extends Error {
2729
context?: HubSpotHttpErrorContext
2830
) {
2931
super(message, options);
30-
this.name = 'HubSpotHttpError';
32+
this.name = HubSpotHttpErrorName;
3133
this.context = context;
3234
this.cause = options?.cause;
3335

@@ -77,11 +79,18 @@ export class HubSpotHttpError<T = any> extends Error {
7779
}
7880
}
7981

80-
public updateContext(context: Partial<HubSpotHttpErrorContext>) {
82+
public updateContext(
83+
context: Partial<HubSpotHttpErrorContext>,
84+
additionalDebugContext?: string
85+
) {
8186
this.context = { ...this.context, ...context };
8287
// Update the error messages when the context is updated
8388
if (isAxiosError(this.cause)) {
84-
this.message = this.joinErrorMessages(this.cause, this.context);
89+
this.message = this.joinErrorMessages(
90+
this.cause,
91+
this.context,
92+
additionalDebugContext
93+
);
8594
}
8695
}
8796

@@ -176,9 +185,9 @@ export class HubSpotHttpError<T = any> extends Error {
176185

177186
private joinErrorMessages(
178187
error: AxiosError<{ message: string; errors: { message: string }[] }>,
179-
context: HubSpotHttpErrorContext = {}
188+
context: HubSpotHttpErrorContext = {},
189+
additionalDebugContext?: string
180190
): string {
181-
const i18nKey = 'errors.apiErrors';
182191
const status = error.response?.status;
183192
const method = error.config?.method as HttpMethod;
184193

@@ -196,57 +205,75 @@ export class HubSpotHttpError<T = any> extends Error {
196205
? `${action} ${preposition} '${context.request}'`
197206
: action;
198207

199-
messageDetail = i18n(`${i18nKey}.messageDetail`, {
208+
messageDetail = i18n(`errors.apiErrors.messageDetail`, {
200209
accountId: context.accountId,
201210
requestName,
202211
});
203212
} else {
204-
messageDetail = i18n(`${i18nKey}.genericMessageDetail`);
213+
messageDetail = i18n(`errors.apiErrors.genericMessageDetail`);
205214
}
206215

207216
const errorMessage: Array<string> = [];
208217

209218
if ((method === 'put' || method === 'post') && context.payload) {
210219
errorMessage.push(
211-
i18n(`${i18nKey}.unableToUpload`, { payload: context.payload })
220+
i18n(`errors.apiErrors.unableToUpload`, { payload: context.payload })
212221
);
213222
}
214223

224+
let statusBasedMessage: string | undefined;
225+
215226
switch (status) {
216227
case 400:
217-
errorMessage.push(i18n(`${i18nKey}.codes.400`, { messageDetail }));
228+
statusBasedMessage = i18n(`errors.apiErrors.codes.400`, {
229+
messageDetail,
230+
});
218231
break;
219232
case 401:
220-
errorMessage.push(i18n(`${i18nKey}.codes.401`, { messageDetail }));
233+
statusBasedMessage = i18n(`errors.apiErrors.codes.401`, {
234+
messageDetail,
235+
});
221236
break;
222237
case 403:
223238
break;
224239
case 404:
225-
errorMessage.push(i18n(`${i18nKey}.codes.404`, { messageDetail }));
240+
statusBasedMessage = i18n(`errors.apiErrors.codes.404`, {
241+
messageDetail,
242+
});
226243
break;
227244
case 429:
228-
errorMessage.push(i18n(`${i18nKey}.codes.429`, { messageDetail }));
245+
statusBasedMessage = i18n(`errors.apiErrors.codes.429`, {
246+
messageDetail,
247+
});
229248
break;
230249
case 503:
231-
errorMessage.push(i18n(`${i18nKey}.codes.503`, { messageDetail }));
250+
statusBasedMessage = i18n(`errors.apiErrors.codes.503`, {
251+
messageDetail,
252+
});
232253
break;
233254
default:
234255
if (status && status >= 500 && status < 600) {
235-
errorMessage.push(
236-
i18n(`${i18nKey}.codes.500Generic`, { messageDetail })
237-
);
256+
statusBasedMessage = i18n(`errors.apiErrors.codes.500Generic`, {
257+
messageDetail,
258+
});
238259
} else if (status && status >= 400 && status < 500) {
239-
errorMessage.push(
240-
i18n(`${i18nKey}.codes.400Generic`, { messageDetail })
241-
);
260+
statusBasedMessage = i18n(`errors.apiErrors.codes.400Generic`, {
261+
messageDetail,
262+
});
242263
} else {
243-
errorMessage.push(
244-
i18n(`${i18nKey}.codes.generic`, { messageDetail })
245-
);
264+
statusBasedMessage = i18n(`errors.apiErrors.codes.generic`, {
265+
messageDetail,
266+
});
246267
}
247268
break;
248269
}
249270

271+
if (statusBasedMessage) {
272+
errorMessage.push(
273+
`${statusBasedMessage}${additionalDebugContext ? ` ${additionalDebugContext}` : ''}`
274+
);
275+
}
276+
250277
if (error?.response?.data) {
251278
const { message, errors } = error.response.data;
252279

models/__tests__/HubSpotHttpError.ts renamed to models/__tests__/HubSpotHttpError.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,50 @@ describe('models/HubSpotHttpError', () => {
133133
payload: 'payload',
134134
});
135135
});
136+
137+
it('should append additional debug context to the error message when provided', () => {
138+
const cause = newAxiosError();
139+
const hubspotHttpError = new HubSpotHttpError('', { cause });
140+
const additionalDebugContext = 'Extra debug info';
141+
hubspotHttpError.updateContext(
142+
{ accountId: portalId },
143+
additionalDebugContext
144+
);
145+
expect(hubspotHttpError.message).toContain(additionalDebugContext);
146+
});
147+
148+
it('should not modify the error message when additional debug context is not provided', () => {
149+
const cause = newAxiosError();
150+
const hubspotHttpError = new HubSpotHttpError('', { cause });
151+
const originalMessage = hubspotHttpError.message;
152+
hubspotHttpError.updateContext({ accountId: portalId });
153+
expect(hubspotHttpError.message).toBe(originalMessage);
154+
});
155+
156+
it('should handle additional debug context when cause is not an AxiosError', () => {
157+
const hubspotHttpError = new HubSpotHttpError('Test error');
158+
const additionalDebugContext = 'Extra debug info';
159+
expect(() => {
160+
hubspotHttpError.updateContext(
161+
{ accountId: portalId },
162+
additionalDebugContext
163+
);
164+
}).not.toThrow();
165+
expect(hubspotHttpError.context).toStrictEqual({ accountId: portalId });
166+
});
167+
168+
it('should append additional debug context with proper spacing', () => {
169+
const cause = newAxiosError();
170+
const hubspotHttpError = new HubSpotHttpError('', { cause });
171+
const additionalDebugContext = 'Debug: Connection timeout';
172+
hubspotHttpError.updateContext(
173+
{ accountId: portalId },
174+
additionalDebugContext
175+
);
176+
expect(hubspotHttpError.message).toMatch(
177+
/was bad\.\s+Debug: Connection timeout/
178+
);
179+
});
136180
});
137181

138182
describe('toString', () => {

0 commit comments

Comments
 (0)