Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add explicit error types for easier debugging #3496

Merged
merged 1 commit into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/1-bug.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ about: Something is not working as it should

- Node.js version:
- Gitbeaker version:
- Gitbeaker release (cli, node, browser, core, requester-utils):
- Gitbeaker release (cli, rest, core, requester-utils):
- OS & version:

<!-- *(Brief description of your issue here)*
Expand Down
31 changes: 31 additions & 0 deletions packages/requester-utils/src/GitbeakerError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-disable max-classes-per-file */
export class GitbeakerRequestError extends Error {
constructor(
message: string,
options?: {
cause: {
description: string;
request: Request;
response: Response;
};
},
) {
super(message, options);

this.name = 'GitbeakerRequestError';
}
}

export class GitbeakerTimeoutError extends Error {
constructor(message: string) {
super(message);
this.name = 'GitbeakerTimeoutError';
}
}

export class GitbeakerRetryError extends Error {
constructor(message: string) {
super(message);
this.name = 'GitbeakerTimeoutError';
}
}
1 change: 1 addition & 0 deletions packages/requester-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './RequesterUtils';
export * from './BaseResource';
export * from './GitbeakerError';
23 changes: 16 additions & 7 deletions packages/rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,17 +339,26 @@ const api = new Gitlab({

Request errors are returned back within a plain Error instance, using the cause to hold the original response and a text description of the error pulled from the response's error or message fields if JSON, or its plain text value:

```js
Error: Bad Request
<stack trace>
{
[cause]: {
description: <text description>,
response: <original Response object>
```ts
class GitbeakerError extends Error {
constructor(
message: string,
options?: {
cause: {
description: string;
request: Request;
response: Response;
};
},
) {
super(message, options);
this.name = 'GitbeakerError';
}
}
```

Note, the message is assigned to the Response's `statusText`, and the [Request](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#request) and [Response](https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#response) types are from the NodeJS API.

## Examples

Once you have your library instantiated, you can utilize many of the API's functionality:
Expand Down
19 changes: 14 additions & 5 deletions packages/rest/src/Requester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import type {
ResourceOptions,
ResponseBodyTypes,
} from '@gitbeaker/requester-utils';
import { createRequesterFn, getMatchingRateLimiter } from '@gitbeaker/requester-utils';
import {
GitbeakerRequestError,
GitbeakerRetryError,
GitbeakerTimeoutError,
createRequesterFn,
getMatchingRateLimiter,
} from '@gitbeaker/requester-utils';

export async function defaultOptionsHandler(
resourceOptions: ResourceOptions,
Expand Down Expand Up @@ -63,7 +69,10 @@ async function parseResponse(response: Response, asStream = false) {
return { body, headers, status };
}

async function throwFailedRequestError(request: Request, response: Response) {
async function throwFailedRequestError(
request: Request,
response: Response,
): Promise<GitbeakerRequestError> {
const content = await response.text();
const contentType = response.headers.get('Content-Type');
let description = 'API Request Error';
Expand All @@ -76,7 +85,7 @@ async function throwFailedRequestError(request: Request, response: Response) {
description = content;
}

throw new Error(response.statusText, {
throw new GitbeakerRequestError(response.statusText, {
cause: {
description,
request,
Expand Down Expand Up @@ -114,7 +123,7 @@ export async function defaultRequestHandler(endpoint: string, options?: RequestO

const response = await fetch(request).catch((e) => {
if (e.name === 'TimeoutError' || e.name === 'AbortError') {
throw new Error('Query timeout was reached');
throw new GitbeakerTimeoutError('Query timeout was reached');
}

throw e;
Expand All @@ -131,7 +140,7 @@ export async function defaultRequestHandler(endpoint: string, options?: RequestO
}
/* eslint-enable */

throw new Error(
throw new GitbeakerRetryError(
`Could not successfully complete this request due to Error 429. Check the applicable rate limits for this endpoint.`,
);
}
Expand Down
10 changes: 5 additions & 5 deletions packages/rest/test/unit/Requester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ describe('defaultRequestHandler', () => {

await expect(defaultRequestHandler('http://test.com', {} as RequestOptions)).rejects.toThrow({
message: 'Really Bad Error',
name: 'Error',
name: 'GitbeakerRequestError',
cause: {
description: 'msg',
},
Expand All @@ -141,7 +141,7 @@ describe('defaultRequestHandler', () => {

await expect(defaultRequestHandler('http://test.com', {} as RequestOptions)).rejects.toThrow({
message: 'Really Bad Error',
name: 'Error',
name: 'GitbeakerRequestError',
cause: {
description: stringBody,
},
Expand All @@ -160,7 +160,7 @@ describe('defaultRequestHandler', () => {

await expect(defaultRequestHandler('http://test.com', {} as RequestOptions)).rejects.toThrow({
message: 'Query timeout was reached',
name: 'Error',
name: 'GitbeakerTimeoutError',
});
});

Expand All @@ -176,7 +176,7 @@ describe('defaultRequestHandler', () => {

await expect(defaultRequestHandler('http://test.com', {} as RequestOptions)).rejects.toThrow({
message: 'Query timeout was reached',
name: 'Error',
name: 'GitbeakerTimeoutError',
});
});

Expand Down Expand Up @@ -287,7 +287,7 @@ describe('defaultRequestHandler', () => {
await expect(defaultRequestHandler('http://test.com', {} as RequestOptions)).rejects.toThrow({
message:
'Could not successfully complete this request due to Error 429. Check the applicable rate limits for this endpoint.',
name: 'Error',
name: 'GitbeakerRetryError',
});

MockFetch.mockRestore();
Expand Down
Loading