From 4c4b00c51fb4d4a180aa4ea02bd89362760275c1 Mon Sep 17 00:00:00 2001 From: behnam Date: Sat, 2 Nov 2024 13:10:17 +0300 Subject: [PATCH] Add tools for failure handling --- package.json | 1 + src/feature/common/data/api-task.ts | 8 ++ src/feature/common/failures/base-failure.ts | 22 +++++ .../common/failures/dev/arguments-failure.ts | 12 +++ .../common/failures/dev/base-dev-failure.ts | 3 + .../common/failures/dev/dependency-failure.ts | 10 ++ .../common/failures/failure-helpers.ts | 95 +++++++++++++++++++ .../common/failures/network-failure.ts | 12 +++ yarn.lock | 5 + 9 files changed, 168 insertions(+) create mode 100644 src/feature/common/data/api-task.ts create mode 100644 src/feature/common/failures/base-failure.ts create mode 100644 src/feature/common/failures/dev/arguments-failure.ts create mode 100644 src/feature/common/failures/dev/base-dev-failure.ts create mode 100644 src/feature/common/failures/dev/dependency-failure.ts create mode 100644 src/feature/common/failures/failure-helpers.ts create mode 100644 src/feature/common/failures/network-failure.ts diff --git a/package.json b/package.json index 8691324..6fd58e3 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@radix-ui/react-slot": "^1.1.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "fp-ts": "^2.16.9", "lucide-react": "^0.454.0", "next": "15.0.2", "postgres": "^3.4.5", diff --git a/src/feature/common/data/api-task.ts b/src/feature/common/data/api-task.ts new file mode 100644 index 0000000..f234898 --- /dev/null +++ b/src/feature/common/data/api-task.ts @@ -0,0 +1,8 @@ +import { Either } from "fp-ts/lib/Either"; +import { TaskEither } from "fp-ts/lib/TaskEither"; +import BaseFailure from "@/feature/common/failures/base-failure"; + +type ApiTask = TaskEither; +export type ApiEither = Either; + +export default ApiTask; diff --git a/src/feature/common/failures/base-failure.ts b/src/feature/common/failures/base-failure.ts new file mode 100644 index 0000000..bc09402 --- /dev/null +++ b/src/feature/common/failures/base-failure.ts @@ -0,0 +1,22 @@ +import { makeFailureMessage } from "@/feature/common/failures/failure-helpers"; + +/** + * This is a class called BaseFailure that extends the Error class. It is + * used as a base class for creating custom failure classes. + */ +export default abstract class BaseFailure { + /* ------------------------------- Attributes ------------------------------- */ + private readonly BASE_FAILURE_MESSAGE = "failure"; + + /* -------------------------------------------------------------------------- */ + /** + * Use this message as key lang for failure messages + */ + message = this.BASE_FAILURE_MESSAGE; + + /* -------------------------------------------------------------------------- */ + constructor(key: string) { + this.message = makeFailureMessage(this.message, key); + } + /* -------------------------------------------------------------------------- */ +} diff --git a/src/feature/common/failures/dev/arguments-failure.ts b/src/feature/common/failures/dev/arguments-failure.ts new file mode 100644 index 0000000..ed4e2c9 --- /dev/null +++ b/src/feature/common/failures/dev/arguments-failure.ts @@ -0,0 +1,12 @@ +import BaseDevFailure from "@/feature/common/failures/dev/base-dev-failure"; + +/** + * Failure for needed arguments in a method but sent wrong one + */ +export default class ArgumentsFailure extends BaseDevFailure { + /* ------------------------------- Constructor ------------------------------ */ + constructor() { + super("arguments"); + } + /* -------------------------------------------------------------------------- */ +} diff --git a/src/feature/common/failures/dev/base-dev-failure.ts b/src/feature/common/failures/dev/base-dev-failure.ts new file mode 100644 index 0000000..aaf8142 --- /dev/null +++ b/src/feature/common/failures/dev/base-dev-failure.ts @@ -0,0 +1,3 @@ +import BaseFailure from "@/feature/common/failures/base-failure"; + +export default abstract class BaseDevFailure extends BaseFailure {} diff --git a/src/feature/common/failures/dev/dependency-failure.ts b/src/feature/common/failures/dev/dependency-failure.ts new file mode 100644 index 0000000..86b49cf --- /dev/null +++ b/src/feature/common/failures/dev/dependency-failure.ts @@ -0,0 +1,10 @@ +import BaseDevFailure from "@/feature/common/failures/dev/base-dev-failure"; + +/** + * This is a failure of not having specific dependency + */ +export default class DependencyFailure extends BaseDevFailure { + constructor() { + super("DependencyFailure"); + } +} diff --git a/src/feature/common/failures/failure-helpers.ts b/src/feature/common/failures/failure-helpers.ts new file mode 100644 index 0000000..a6c8f46 --- /dev/null +++ b/src/feature/common/failures/failure-helpers.ts @@ -0,0 +1,95 @@ +import BaseFailure from "@/feature/common/failures/base-failure"; + +/** + * This method is supposed to save previous failure of TaskEither + * to prevent it from loosing and overriding by the new one. + * + * Usage example: + * ```ts + * tryCatch( + * async () => { + * ... + * throw ValidationFailure(); + * ... + * }, + * (reason) => failureOr(reason, new UserCreationFailure()), + * ) + * ``` + * In this example `failureOr` will return already throwed + * instance of `BaseFailure` which is `ValidationFailure`. + * + * + * @param reason is throwed object. + * Basically it can be default `Error` or instance of `BaseFailure`. + * @param failure instance of `BaseFailure` that will be returned + * if reason is not instance of `BaseFailure`. + * @returns `BaseFailure` + */ +export function failureOr(reason: unknown, failure: BaseFailure): BaseFailure { + if (reason instanceof BaseFailure) { + return reason; + } + return failure; +} + +/** + * Returns a function that maps a BaseFailure instance to a new BaseFailure instance of type IfType using the provided mapping function. + * @param f A function that maps an instance of IfType to a new instance of BaseFailure. + * @param ctor A constructor function for IfType. + * @returns A function that maps a BaseFailure instance to a new BaseFailure instance of type IfType. + */ +export function mapToFailureFrom( + f: (t: IfType) => BaseFailure, + ctor: new (...args: never[]) => IfType, +): (t: BaseFailure) => BaseFailure { + return mapIfInstance(f, ctor); +} + +/** + * Maps an instance of a class to a response using a provided function. + * + * @template IfType - The type of the instance to map. + * @template Response - The type of the response to map to. + * @param {function} f - The function to use to map the instance to a response. + * @param {new (...args: never[]) => IfType} ctor - The constructor function of the instance to map. + * @returns {(t: IfType | Response) => IfType | Response} - A function that maps the instance to a response using the provided function. + */ +export function mapIfInstance( + f: (t: IfType) => Response, + ctor: new (...args: never[]) => IfType, +) { + return (t: IfType | Response) => { + if (t instanceof ctor) { + return f(t); + } + return t; + }; +} + +/** + * Maps a function to a value if it is not an instance of a given class. + * @template IfType The type of the value to be mapped. + * @template Response The type of the mapped value. + * @param {function} f The function to map the value with. + * @param {new (...args: never[]) => IfType} ctor The class to check the value against. + * @returns {function} A function that maps the value if it is not an instance of the given class. + */ +export function mapIfNotInstance( + f: (t: IfType) => Response, + ctor: new (...args: never[]) => IfType, +) { + return (t: IfType | Response) => { + if (t! instanceof ctor) { + return f(t); + } + return t; + }; +} + +/** + * Gets Message key and it'll add it to the failure message key hierarchy + */ +export function makeFailureMessage(message: string, key: string) { + if (!key) return message; + return `${message}.${key}`; +} diff --git a/src/feature/common/failures/network-failure.ts b/src/feature/common/failures/network-failure.ts new file mode 100644 index 0000000..3268fc3 --- /dev/null +++ b/src/feature/common/failures/network-failure.ts @@ -0,0 +1,12 @@ +import BaseFailure from "./base-failure"; + +/** + * Failure for HTTP response when response dosn't have base structure + */ +export default class NetworkFailure extends BaseFailure { + /* ------------------------------- Constructor ------------------------------ */ + constructor() { + super("network"); + } + /* -------------------------------------------------------------------------- */ +} diff --git a/yarn.lock b/yarn.lock index 6d7dde4..194f43b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2125,6 +2125,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +fp-ts@^2.16.9: + version "2.16.9" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.16.9.tgz#99628fc5e0bb3b432c4a16d8f4455247380bae8a" + integrity sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ== + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"