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

Feat/prepare release #5

Merged
merged 14 commits into from
May 18, 2024
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
24 changes: 24 additions & 0 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Test changes

on:
pull_request:

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'latest'
registry-url: 'https://registry.npmjs.org/'

- name: Install dependencies
run: yarn install

- name: Test project
run: yarn test
3 changes: 3 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
- name: Install dependencies
run: yarn install

- name: Test project
run: yarn test

- name: Build project
run: yarn build

Expand Down
60 changes: 55 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pnpm install network-error-handling

## Usage

### Network error handling

1. Import

```typescript
Expand All @@ -28,18 +30,21 @@ import { networkErrorHandling } from "network-error-handling";

2. Call the function, example:

> [!IMPORTANT]
> The `error` parameter is the `AxiosError` object from the axios response.

```typescript
networkErrorHandling(error)
.addError(
StatusCode.CONFLICT,
409,
"User already exists",
"The user already exists, please try again with another email.",
() => console.log("Error while creating user")
)
.handle();
```

## Using toast
#### Using toast

If you plan to display a toast of error, you will need to have a function to handle the toast, example using Shadcn
UI toast component
Expand All @@ -56,7 +61,12 @@ After this, you can use the `withToast` method to handle the toast, example:
```typescript
networkErrorHandling(error)
.addError(
StatusCode.CONFLICT,
400,
"Invalid form data",
"The form data is invalid, please check the fields."
)
.addError(
409,
"User already exists",
"The user already exists, please try again with another email."
)
Expand All @@ -69,7 +79,7 @@ Ooh, you can also use a callback function when using a toast!
```typescript
networkErrorHandling(error)
.addError(
StatusCode.CONFLICT,
409,
"User already exists",
"The user already exists, please try again with another email.",
() => console.log("Error while creating user")
Expand All @@ -78,6 +88,46 @@ networkErrorHandling(error)
.handle();
```

### Status Codes

This package provides a list of status codes that you can use to handle the errors, example:

```typescript
import { StatusCode } from "network-error-handling";

networkErrorHandling(error)
.addError(
StatusCode.CONFLICT,
"User already exists",
"The user already exists, please try again with another email."
)
.handle();
```

## Api References

### `networkErrorHandling(error: AxiosError)`

- `error`: The `AxiosError` object from the axios response.

### `addError(statusCode: number, title: string, description: string, callback?: () => void)`

- `statusCode`: The status code to handle the error.
- `title`: The title of the error.
- `description`: The description of the error.
- `callback`: Optional callback function to execute when the error is handled.

> [!CAUTION]
> For now, the callback parameter is not async aware, so you can't use async functions inside the callback.

### `withToast(toastFunction: (title: string, description: string) => void)`

- `toastFunction`: The function to handle the toast.

### `handle()`

- Handle the error.

## License

[MIT](LICENSE)
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"version": "1.0.3",
"description": "An useful error handler for axios network requests in a React application",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/vboechat/network-error-handling"
},
"author": {
"name": "Victor Ribeiro Boechat",
"email": "[email protected]",
Expand All @@ -15,12 +19,12 @@
"dist"
],
"scripts": {
"build": "tsc"
},
"dependencies": {
"axios": "^1.6.8"
"build": "tsc",
"test": "vitest"
},
"devDependencies": {
"typescript": "^5.4.5"
"axios": "^1.6.8",
"typescript": "^5.4.5",
"vitest": "^1.6.0"
}
}
125 changes: 125 additions & 0 deletions src/network-error-handling.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { describe, it, expect, vi } from "vitest";
import { AxiosError } from "axios";
import { networkErrorHandling, ToastFunction } from "./network-error-handling";
import { StatusCode } from "./status-codes";

describe("networkErrorHandling", () => {
it("should add an error and handle it without toast", () => {
const axiosError = {
response: { status: 404 },
} as AxiosError;

const handler = networkErrorHandling(axiosError).addError(
StatusCode.NOT_FOUND,
"Not Found",
"The requested resource was not found"
);

const callback = vi.fn();
handler.addError(
StatusCode.INTERNAL_SERVER_ERROR,
"Server Error",
"Internal server error",
callback
);

handler.handle();

expect(callback).not.toHaveBeenCalled();
});

it("should add an error and handle it with toast", () => {
const axiosError = {
response: { status: 404 },
} as AxiosError;

const toast: ToastFunction = vi.fn();

const handler = networkErrorHandling(axiosError)
.addError(
StatusCode.NOT_FOUND,
"Not Found",
"The requested resource was not found"
)
.withToast(toast);

handler.handle();

expect(toast).toHaveBeenCalledWith(
"Not Found",
"The requested resource was not found"
);
});

it("should add multiple errors and handle them correctly", () => {
const axiosError = {
response: { status: 500 },
} as AxiosError;

const toast: ToastFunction = vi.fn();
const callback = vi.fn();

const handler = networkErrorHandling(axiosError)
.addError(
StatusCode.NOT_FOUND,
"Not Found",
"The requested resource was not found"
)
.addError(
StatusCode.INTERNAL_SERVER_ERROR,
"Server Error",
"Internal server error",
callback
)
.withToast(toast);

handler.handle();

expect(toast).toHaveBeenCalledWith("Server Error", "Internal server error");
expect(callback).toHaveBeenCalled();
});

it("should not handle error if status is not in the map", () => {
const axiosError = {
response: { status: 403 },
} as AxiosError;

const toast: ToastFunction = vi.fn();
const callback = vi.fn();

const handler = networkErrorHandling(axiosError)
.addError(
StatusCode.NOT_FOUND,
"Not Found",
"The requested resource was not found"
)
.addError(
StatusCode.INTERNAL_SERVER_ERROR,
"Server Error",
"Internal server error",
callback
)
.withToast(toast);

handler.handle();

expect(toast).not.toHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
});

it("should handle error without status", () => {
const axiosError = {
response: {},
} as AxiosError;

const handler = networkErrorHandling(axiosError).addError(
StatusCode.NOT_FOUND,
"Not Found",
"The requested resource was not found"
);

handler.handle();

expect(true).toBe(true);
});
});
35 changes: 9 additions & 26 deletions src/network-error-handling.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { AxiosError } from "axios";
import { ToastFunction } from "./types/toast-function";

/**
* Interface for error handling.
*/
interface ErrorHandler {
export type ToastFunction = (title: string, description: string) => void;

export type ErrorHandler = {
/**
* Adds an error with status, title, message, and optional callback.
* @param {number} status - HTTP status code.
Expand All @@ -19,12 +17,9 @@ interface ErrorHandler {
message: string,
callback?: () => void
) => ErrorHandlerWithHandle;
}
};

/**
* Interface for error handling with both addError and handle methods.
*/
interface ErrorHandlerWithHandle {
export type ErrorHandlerWithHandle = {
/**
* Adds an error with status, title, message, and optional callback.
* @param {number} status - HTTP status code.
Expand All @@ -51,22 +46,16 @@ interface ErrorHandlerWithHandle {
* Handles the error by executing the callback.
*/
handle: () => void;
}
};

/**
* Interface for error handling with toast notifications and handle method.
*/
interface ErrorHandlingWithToastHandle {
export type ErrorHandlingWithToastHandle = {
/**
* Handles the error by executing the callback and/or displaying a toast.
*/
handle: () => void;
}
};

/**
* Type for error details.
*/
type ErrorDetails = {
export type ErrorDetails = {
title: string;
message: string;
callback?: () => void;
Expand All @@ -80,12 +69,6 @@ type ErrorDetails = {
export function networkErrorHandling(error: AxiosError): ErrorHandler {
const errors = new Map<number, ErrorDetails>();

/**
* Creates an error handler with the current errors and toast function.
* @param {Map<number, ErrorDetails>} currentErrors - Map of current error details.
* @param {ToastFunction | null} currentToast - Current toast function, if any.
* @returns {ErrorHandler & ErrorHandlerWithHandle} - Returns the ErrorHandler instance.
*/
const createHandler = (
currentErrors: Map<number, ErrorDetails>,
currentToast: ToastFunction | null
Expand Down
34 changes: 34 additions & 0 deletions src/status-codes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const StatusCode = {
OK: 200,
CREATED: 201,
ACCEPTED: 202,
NO_CONTENT: 204,
MOVED_PERMANENTLY: 301,
FOUND: 302,
SEE_OTHER: 303,
NOT_MODIFIED: 304,
TEMPORARY_REDIRECT: 307,
PERMANENT_REDIRECT: 308,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
METHOD_NOT_ALLOWED: 405,
NOT_ACCEPTABLE: 406,
CONFLICT: 409,
GONE: 410,
LENGTH_REQUIRED: 411,
PRECONDITION_FAILED: 412,
PAYLOAD_TOO_LARGE: 413,
URI_TOO_LONG: 414,
UNSUPPORTED_MEDIA_TYPE: 415,
RANGE_NOT_SATISFIABLE: 416,
EXPECTATION_FAILED: 417,
UPGRADE_REQUIRED: 426,
INTERNAL_SERVER_ERROR: 500,
NOT_IMPLEMENTED: 501,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIMEOUT: 504,
HTTP_VERSION_NOT_SUPPORTED: 505,
} as const;
1 change: 0 additions & 1 deletion src/types/toast-function.ts

This file was deleted.