From dd36974389eb2af94eb6b0f3abd30a109d4a577f Mon Sep 17 00:00:00 2001 From: INeedJobToStartWork Date: Tue, 8 Oct 2024 13:02:02 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Refactor(oh-my-error)!:?= =?UTF-8?q?=20Refactoring=20and=20adding=20mass=20of=20new=20features=20to?= =?UTF-8?q?=20prepare=20package=20to=20version=20`2.0.0`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 14 ++ README.md | 213 +++++++++++++++++- README.npm.md | 40 ---- config/tsuprc/tsup.prod.ts | 2 +- eslint.config.js | 6 +- package.json | 3 +- src/functions/index.ts | 4 + src/functions/myError.test.ts | 59 +++++ src/functions/myError.ts | 80 +++++++ src/{utils => functions}/myErrorCatcher.ts | 3 +- src/functions/myErrorHandler.test.ts | 25 ++ src/functions/myErrorHandler.ts | 51 +++++ .../myErrorWrapper.test.ts | 31 ++- src/{utils => functions}/myErrorWrapper.ts | 19 +- src/index.ts | 20 +- .../componentConcept/componentConcept.test.ts | 57 ----- src/tests/componentConcept/fileToRead.txt | 2 +- src/tests/componentConcept/readFile.test.ts | 22 ++ src/tests/componentConcept/readFile.ts | 34 +-- src/types/errors.ts | 131 +++++++++++ src/types/index.ts | 2 + src/{utils/types.ts => types/internal.ts} | 34 +-- src/types/statusCodes.ts | 76 +++++++ src/utils/index.ts | 5 +- src/utils/isPromise.ts | 5 + src/utils/myError.ts | 17 -- src/utils/myErrorHandler.ts | 33 --- 27 files changed, 770 insertions(+), 218 deletions(-) delete mode 100644 README.npm.md create mode 100644 src/functions/index.ts create mode 100644 src/functions/myError.test.ts create mode 100644 src/functions/myError.ts rename src/{utils => functions}/myErrorCatcher.ts (62%) create mode 100644 src/functions/myErrorHandler.test.ts create mode 100644 src/functions/myErrorHandler.ts rename src/{utils => functions}/myErrorWrapper.test.ts (58%) rename src/{utils => functions}/myErrorWrapper.ts (52%) delete mode 100644 src/tests/componentConcept/componentConcept.test.ts create mode 100644 src/tests/componentConcept/readFile.test.ts create mode 100644 src/types/errors.ts create mode 100644 src/types/index.ts rename src/{utils/types.ts => types/internal.ts} (60%) create mode 100644 src/types/statusCodes.ts create mode 100644 src/utils/isPromise.ts delete mode 100644 src/utils/myError.ts delete mode 100644 src/utils/myErrorHandler.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd4d0d..38930ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # oh-my-error +## 2.0.0-prerelease.0 + +### Major Changes + +- Add new Error Types, New Functions and Features to current existing functions +- Add `statusCodes` enum. +- Refactored `MyError` => `myError`, currently it's error object handler before throw. +- Added new Feature to `myErrorWrapper` - Instant throw error, no need to use condition with `isError` to throw Error. +- Added `myErrorWrapperAsync` which is `myErrorWrapper` alternative for async functions. +- Refactored `TMyError` - renamed `TMyError` => `IMyError`, changed from `type` to `interface` and structure. +- Add new Error (Organism) Interfaces `IMyError`,`IMyErrorAPI`,`IMyErrorRateLimit`,`IMyErrorValidation` +- Add new Error (Atom) types + `TSeverity`,`TSeverity2`,`TErrorMessages`,`TErrorMessagesExt`,`TMyErrorList`,`TErrorList`,`TCauseError`,`TDetails`,`TBaseError`,`TBaseErrorExt`,`TValidationError`,`TApiError`,`TApiRateLimit`, + ## 1.1.1 ### Patch Changes diff --git a/README.md b/README.md index 1f8fbab..5db7263 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,53 @@ ![image](https://github.com/INeedJobToStartWork/MyError/assets/97305201/03fa3e50-af28-4345-a3f7-f84d091b4eb1)

MyError

-

Very Clean Error Handler!

- +

A Very Clean Error Handler!

+
+ 📗Tutorials (in soon) | + 📘Docs (in soon) | + 🏗️Contributing Guide +

+Benefits: + +- All Errors in one Place +- Easier in Maintaining code (No more `trycatches`) +- + +## **Showcase** + +```ts +// Make your life better + +// Instead +let data; +try { + data = readFile("...path"); +} catch { + throw Error("Cant Load File!"); +} + +// Do +const data = myErrorWrapper(readFile, Error("Cant Load File!"))("...path"); +``` + # 📜 List of Contest -- [Install](#install) -- [Types](#types) -- [Functions](#functions) - - [myErrorWrapper](#myErrorWrapper) - - [myErrorCatcher](#myErrorCatcher) - - [myErrorHandler](#myErrorHandler) -- [Objects](#Objects) - - [MyErrorList](#MyErrorList) - - [MyHandler](#MyHandler) -- [Sample of Code](#sample-of-code) +- [📜 List of Contest](#-list-of-contest) + - [Install](#install) + - [TLDR (Only Most Important!)](#tldr-only-most-important) + - [Functions](#functions) + - [Types](#types) + - [Functions](#functions-1) + - [myError `♻️ Refactored`](#myerror-️-refactored) + - [myErrorWrapper/Async `🎉 New feature!`](#myerrorwrapperasync--new-feature) + - [myErrorCatcher](#myerrorcatcher) + - [myErrorHandler](#myerrorhandler) + - [Types](#types-1) + - [TMyErrorList](#tmyerrorlist) + - [Error Templates (Interfaces)](#error-templates-interfaces) + - [Predefined elements for Functions (Atoms)](#predefined-elements-for-functions-atoms) ## Install @@ -37,3 +68,161 @@ Yarn ```bash copy yarn add oh-my-error ``` + +## TLDR (Only Most Important!) + +### Functions + +| Name | Description | +| ---------------------------------------------- | ------------------------------- | +| [myError](#myerror-️-refactored) | Handle with Error Object | +| [myErrorWrapper](#myerrorwrapper--new-feature) | `trycatch` wrapper one liner | +| [myErrorCatcher](#myerrorcatcher) | `promise` wrapper | +| [myErrorHandler](#myerrorhandler) | handle scenarios for each error | + +### Types + +**Error Templates** + +| Name | Description | +| ------------------------------------------------- | --------------------------------------------------------------- | +| [TMyErrorList](#tmyerrorlist) `!important` | For creating list with errors in one place! | +| [IMyError](#error-templates-interfaces) | Basic Error | +| [IMyErrorAPI](#error-templates-interfaces) | Basic Error API | +| [IMyErrorRateLimit](#error-templates-interfaces) | [IMyErrorApi](#error-templates-interfaces) with RateLimit error | +| [IMyErrorValidation](#error-templates-interfaces) | [IMyError](error-templates-interfaces) with Validation error | + +**[Rest Types for creating own Errors](#predefined-elements-for-functions-atoms)** + +## Functions + +### myError `♻️ Refactored` + +Processes an error object, invoking functions with provided arguments + +```ts +const MyErrorList = { + BLACKLISTED: { + name: "Black Listed Name", + hint: "Try use other one!", + message: (name: string) => `${name} is on black list!` + } +} as const satisfies TMyErrorList; + +throw new myError(MyErrorList.BLACKLISTED, { message: ["nameInputed"] }); +``` + +**Output** + +```ts +{ + name:"Black Listed Name", + hint:"Try use other one!", + message:"nameInputed is on black list!" +} +``` + +### myErrorWrapper/Async `🎉 New feature!` + +`trycatch` wrapper with instant error thrower. + +```ts +// Before +let data; +try { + data = readFile("path..."); +} catch(){ + throw new Error("Can't read file!"); +} + +// After + +const [data,isError] = myErrorWrapper(readFile)("path..."); +if(isError) throw new Error("Can't read file!") + +// Or instant Error Throw + +const data = myErrorWrapper(readFile,new Error("Can't read file!"))("path..."); +``` + +### myErrorCatcher + +`new Promise` wrapper. + +```ts +const data = await myErrorCatcher(readFile)("path...").catch(() => { + // Code before crash... + throw new Error("Can't read file!"); +}); +``` + +### myErrorHandler + +Execute Scenarios for an error! + +```ts +const [data, isError] = myErrorWrapper(readFile)("./ReadThisFile"); + +const MyErrorHandlerList = { + FS001: () => { + // Do this code if throw this error + console.error("ERROR"); + } +}; +if (isError) await myErrorHandler(data.code, MyErrorHandlerList)(); +``` + +## Types + +### TMyErrorList + +> [!IMPORTANT] +> Use `as const satisfies TMyErrorList` to work it properly.
**Don't** forget about `const` because without this +> you not gonna get tips. + +> [!TIP] +> You can add `satisfies ERRORTYPE` per error to have strict typing per error or just add +> `as const satisfies TMyErrorList` to have strict typing too! + +```ts +const ErrorList = { + notFound: { + name: "Not Found", + code: "FS001", + message: { user: "File not found", dev: "The file you are trying to read does not exist" }, + hint: (path: string) => `Check if the file exists at ${path}` + } satisfies IMyError, + cantRead: { + code: "FS002", + name: "Cant Read", + message: { user: "Can't read file", dev: "readFileSync throw error" }, + hint: { + user: "Check if the file is not corrupted or permissions", + dev: "File is corrupted or has no permissions to be read" + } + } +} as const satisfies TMyErrorList; +``` + +### Error Templates (Interfaces) + +There you can find ready error structures. + +| Name | Description | Extends | +| ------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- | +| IMyError `new!` | Basic Error | - | +| IMyErrorAPI `new!` | Basic Error for an API error | [IMyError](#error-templates-interfaces), [TApiError](#predefined-elements-for-functions-atoms) | +| IMyErrorRateLimit `new!` | API error for RateLimit | [IMyError](#error-templates-interfaces), [TApiRateLimit](#predefined-elements-for-functions-atoms) | +| IMyErrorValidation `new!` | API error for Validation problems | [IMyError](#error-templates-interfaces), [TValidationError](#predefined-elements-for-functions-atoms) | + +### Predefined elements for Functions (Atoms) + +Short predefined types to easy creating own Error types! + +| Name (Col1) | Name (Col2) | Name (Col3) | +| ------------------------ | ----------------------- | --------------------- | +| TSeverity `new!` | TSeverity2 `new!` | TErrorMessages `new!` | +| TErrorMessagesExt `new!` | TMyErrorList `new!` | TErrorList `new!` | +| TCauseError `new!` | TDetails `new!` | TBaseError `new!` | +| TBaseErrorExt `new!` | TValidationError `new!` | TApiError `new!` | +| TApiRateLimit `new!` | | | diff --git a/README.npm.md b/README.npm.md deleted file mode 100644 index 0af88f1..0000000 --- a/README.npm.md +++ /dev/null @@ -1,40 +0,0 @@ -![image](https://github.com/INeedJobToStartWork/MyError/assets/97305201/03fa3e50-af28-4345-a3f7-f84d091b4eb1) - -

MyError

-

Very Clean Error Handler!

-

Rest of the README.md at [Github](https://github.com/INeedJobToStartWork/MyError#readme)

- -
- -## Install - -NPM - -```bash copy -npm install oh-my-error -``` - -PNPM - -```bash copy -pnpm add oh-my-error -``` - -Yarn - -```bash copy -yarn add oh-my-error -``` - -# 📜 List of Contest - -- [Install](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#install) -- [Types](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#types) -- [Functions](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#functions) - - [myErrorWrapper](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#myErrorWrapper) - - [myErrorCatcher](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#myErrorCatcher) - - [myErrorHandler](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#myErrorHandler) -- [Objects](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#Objects) - - [MyErrorList](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#MyErrorList) - - [MyHandler](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#MyHandler) -- [Sample of Code](https://github.com/INeedJobToStartWork/MyError?tab=readme-ov-file#sample-of-code) diff --git a/config/tsuprc/tsup.prod.ts b/config/tsuprc/tsup.prod.ts index c5b6270..133666e 100644 --- a/config/tsuprc/tsup.prod.ts +++ b/config/tsuprc/tsup.prod.ts @@ -17,7 +17,7 @@ export default defineConfig({ { from: "./package.json", to: "./package.json" }, { from: "./.npmrc", to: "./.npmrc" }, { from: "./.npmignore", to: "./.npmignore" }, - { from: "./README.npm.md", to: "./README.md" } + { from: "./README.md", to: "./README.md" } ] }) ] diff --git a/eslint.config.js b/eslint.config.js index 7a2b0ea..1838a38 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -22,4 +22,8 @@ export default ineedj({ yaml: false, ignoreGlobalFiles: { gitIgnore: true, basicIgnores: true } } -}).removeRules("@typescript-eslint/no-throw-literal" /** Use custom Error */); +}).removeRules( + "@typescript-eslint/no-throw-literal" /** Use custom Error */, + "@EslintSecurity/detect-object-injection", + "MD010/no-hard-tabs" +); diff --git a/package.json b/package.json index 01df042..9228ca9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "oh-my-error", - "version": "1.1.1", + "version": "2.0.0-prerelease.0", + "private": true, "description": "A simple error handler for nodejs", "keywords": [ "error", diff --git a/src/functions/index.ts b/src/functions/index.ts new file mode 100644 index 0000000..cf74b5b --- /dev/null +++ b/src/functions/index.ts @@ -0,0 +1,4 @@ +export * from "./myErrorCatcher"; +export * from "./myErrorWrapper"; +export * from "./myErrorHandler"; +export * from "./myError"; diff --git a/src/functions/myError.test.ts b/src/functions/myError.test.ts new file mode 100644 index 0000000..8470343 --- /dev/null +++ b/src/functions/myError.test.ts @@ -0,0 +1,59 @@ +/* eslint-disable @EslintSonar/no-duplicate-string */ +import { describe, it, expect } from "vitest"; +import { myError } from "./myError"; +import type { IMyError, TMyErrorList } from "@/types"; + +describe("[FUNCTION] myError", () => { + it("should return a basic error object", () => { + const errorObj: IMyError = { + name: "Test Error", + message: "This is a test error", + code: 500 + }; + + const result = myError(errorObj); + expect(result).toEqual(errorObj); + }); + it("Example from Function should pass", () => { + const MyErrorList = { + BLACKLISTED: { + name: "Black Listed Name", + hint: "Try use other one!", + message: (name: string) => `${name} is on black list!` + } + } as const satisfies TMyErrorList; + + expect(myError(MyErrorList.BLACKLISTED, { message: ["nameInputed"] })).toEqual({ + ...MyErrorList.BLACKLISTED, + message: (() => MyErrorList.BLACKLISTED.message("nameInputed"))() + }); + }); + it("should return solved test", () => { + const errorObj = { + name: "Black Listed Name", + message: (name: string, idNumber: number) => `${name} with ${idNumber} is blacklisted!`, + code: 500 + } as const satisfies IMyError; + + const result = myError(errorObj, { message: ["BadName", 123] }); + expect(result).toEqual({ ...errorObj, message: `BadName with 123 is blacklisted!` }); + }); + it("Should not overwrite current keys which are not functions", () => { + const errorObj = { + name: "Black Listed Name", + message: (name: string, idNumber: number) => `${name} with ${idNumber} is blacklisted!`, + code: 500 + } as const satisfies IMyError; + const result = myError(errorObj, { message: ["BadName", 123] }); + expect(result).toEqual({ ...errorObj, message: `BadName with 123 is blacklisted!` }); + }); + it("Should not add new keys", () => { + const errorObj = { + name: "Black Listed Name", + message: (name: string, idNumber: number) => `${name} with ${idNumber} is blacklisted!`, + code: 500 + } as const satisfies IMyError; + const result = myError(errorObj, { message: ["BadName", 123], nonono: "lololo" }); + expect(result).toEqual({ ...errorObj, message: `BadName with 123 is blacklisted!` }); + }); +}); diff --git a/src/functions/myError.ts b/src/functions/myError.ts new file mode 100644 index 0000000..5749e19 --- /dev/null +++ b/src/functions/myError.ts @@ -0,0 +1,80 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import type { IMyError } from "@/types"; + +/** + * Represents an error object that may contain nested objects and functions. + */ +type ErrorObject = Record; + +/** + * Helper type to extract function parameters or never for non-functions. + */ +// type FunctionParameters = T extends (...args: unknown[]) => unknown ? Parameters : never; + +/** + * Type representing the structure of arguments for the myError function. + * Maps each key from T to an array of parameters if the value is a function, + * or recursively to the same type if the value is an object. + */ + +type FunctionParameters = T extends (...args: any[]) => any ? Parameters : never; + +type ErrorArgs = { + [K in keyof T]?: T[K] extends (...args: any[]) => any + ? FunctionParameters + : T[K] extends object + ? ErrorArgs + : T[K]; +}; + +/** + * Processes an error object, invoking functions with provided arguments + * + * @param errorObj - The error object to process. + * @param args - Optional arguments to call functions in the error object. + * @returns Processed error object with invoked functions and processed nested objects. + * + * @example + * ``` + * const MyErrorList = { + * BLACKLISTED:{ + * name:"Black Listed Name", + * hint:"Try use other one!", + * message:(name:string)=>`${name} is on black list!` + * } + * } as const satisfies TMyErrorList + * + * throw new myError(MyErrorList.BLACKLISTED,{message:["nameInputed"]}) + * // Throwed Element will look like: + * { + * name:"Black Listed Name", + * hint:"Try use other one!", + * message:"nameInputed is on black list!" + * } + * ``` + */ + +export function myError = IMyError>(errorObj: T, args?: ErrorArgs): T { + const result = { ...errorObj }; + + if (args) { + for (const key in args) { + if (!(key in result)) continue; // Check that `key` from args exist in result (errorT) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + if (typeof result[key] === "function") { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + result[key] = (result[key] as (...args: any[]) => any)(...(args[key] as any[])); // TODO: Change this later and remove eslint ignore above + } else if (typeof result[key] === "object" && result[key] !== null) { + result[key] = myError( + result[key] as ErrorObject & T[Extract], + args[key] as ErrorArgs]> + ); + } + } + } + + return result; +} diff --git a/src/utils/myErrorCatcher.ts b/src/functions/myErrorCatcher.ts similarity index 62% rename from src/utils/myErrorCatcher.ts rename to src/functions/myErrorCatcher.ts index d364089..cc2ef86 100644 --- a/src/utils/myErrorCatcher.ts +++ b/src/functions/myErrorCatcher.ts @@ -1,9 +1,10 @@ -import type { arrowFunction } from "@/utils/types"; +import type { arrowFunction } from "@/types/internal"; export const myErrorCatcher = >(functionThatMayThrow: T) => async (...arguments_: Parameters): Promise> => new Promise(resolve => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument resolve(functionThatMayThrow(...arguments_)); }); diff --git a/src/functions/myErrorHandler.test.ts b/src/functions/myErrorHandler.test.ts new file mode 100644 index 0000000..61e84c4 --- /dev/null +++ b/src/functions/myErrorHandler.test.ts @@ -0,0 +1,25 @@ +import { readFile, ErrorList as readErrorList } from "@/tests/componentConcept/readFile"; +import { describe, expect, it } from "vitest"; +import myErrorWrapper from "./myErrorWrapper"; +import { myError } from "./myError"; +import { join } from "node:path"; +import myErrorHandler from "./myErrorHandler"; + +describe("[FUNCTION] myErrorHandler", () => { + it("ased", async () => { + const pathToFile = join(import.meta.dirname, "./OLOLOLOLOLO"); + const [data, isError] = myErrorWrapper(readFile)(pathToFile); + + expect(isError).toBe(true); + expect(data).toEqual(myError(readErrorList.notFound, { hint: [pathToFile] })); + + const MyErrorHandlerList = { + FS001: () => { + console.error("ERROR"); + return true; + } + }; + const test = await myErrorHandler("FS001", MyErrorHandlerList)(); + expect(test).toBe(true); + }); +}); diff --git a/src/functions/myErrorHandler.ts b/src/functions/myErrorHandler.ts new file mode 100644 index 0000000..a8c989f --- /dev/null +++ b/src/functions/myErrorHandler.ts @@ -0,0 +1,51 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { myError, myErrorWrapperAsync } from "@/functions"; +import type { TMyErrorList } from "@/types"; + +const ErrorList = { + noKeyInList: { + code: "EH001", + name: "Not found key in list", + message: { dev: (errorName: string) => `${errorName} (key) not found in errorSolutions (object)` }, + hint: { dev: "Check if errorName is in errorSolutions" } + }, + executionError: { + code: "EH002", + name: "Error in Execution", + message: { dev: "Error in execution of Solution" }, + hint: { + dev: (errCode: string, args: unknown) => + `Check if the function "errorSolutions[${errCode}](${args})" is working properly` + } + } +} as const satisfies TMyErrorList; + +/** + * Creates an error handler function that executes a specific error solution based on the provided error code. + * + * + * @param errorCode - The error code to handle. + * @param errorSolutions - An object containing error handling functions, keyed by error codes. + * + * @returns An async function that: + * 1. Checks if the error code exists in the solutions. + * 2. If not, throws a "noKeyInList" error. + * 3. If it exists, executes the corresponding error solution function. + * 4. Wraps the execution in a try-catch block to handle any errors during execution. + * + */ +export const myErrorHandler = + >(errorCode: T, errorSolutions: K) => + async (...args: Parameters): Promise> => { + if (!(errorCode in errorSolutions)) { + throw myError(ErrorList.noKeyInList, { message: { dev: [errorCode.toString()] } }); + } + + return myErrorWrapperAsync( + errorSolutions[errorCode], + myError(ErrorList.executionError, { hint: { dev: [errorCode.toString(), args] } }) + )(...args); + }; + +// TODO: Maybe add Sync Alternative, but this works for everything +export default myErrorHandler; diff --git a/src/utils/myErrorWrapper.test.ts b/src/functions/myErrorWrapper.test.ts similarity index 58% rename from src/utils/myErrorWrapper.test.ts rename to src/functions/myErrorWrapper.test.ts index 7397972..e2b34a7 100644 --- a/src/utils/myErrorWrapper.test.ts +++ b/src/functions/myErrorWrapper.test.ts @@ -1,7 +1,11 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { describe, expect, test } from "vitest"; -import myErrorWrapper from "./myErrorWrapper"; -import MyError from "./myError"; -import type { TMyErrorList } from "./types"; +import myErrorWrapper, { myErrorWrapperAsync } from "./myErrorWrapper"; +import { myError } from "./myError"; +import type { TMyErrorList } from "@/types"; describe("[FUNCTION] myErrorWrapper", () => { describe("[PASS]", () => { @@ -19,6 +23,20 @@ describe("[FUNCTION] myErrorWrapper", () => { expect(isError).toEqual(false); expect(data).toEqual(3); }); + test("Async Single ()", async () => { + const sum = async (sum1: number, sum2: number) => (await sum1) + sum2; + + const [data, isError] = await myErrorWrapperAsync(async () => sum(1, 2))(); + expect(isError).toEqual(false); + expect(data).toEqual(3); + }); + test("Async Double ()", async () => { + const sum = async (sum1: number, sum2: number) => (await sum1) + sum2; + + const [data, isError] = await myErrorWrapperAsync(sum)(1, 2); + expect(isError).toEqual(false); + expect(data).toEqual(3); + }); }); describe("[ERROR]", () => { test("Double ()", () => { @@ -44,8 +62,7 @@ describe("[FUNCTION] myErrorWrapper", () => { TOO_HIGH_NUMBER: { code: "TOO_HIGH_NUMBER", name: "Num is too high.", - message: { dev: "One of numbers is >10" }, - hint: {} + message: "One of numbers is >10" } } satisfies TMyErrorList; const sumMax10 = (sum1: number, sum2: number) => { @@ -53,10 +70,10 @@ describe("[FUNCTION] myErrorWrapper", () => { return sum1 + sum2; }; const [error, isError] = myErrorWrapper(() => - myErrorWrapper(sumMax10, new MyError(MyErrorList.TOO_HIGH_NUMBER))(1, 20) + myErrorWrapper(sumMax10, myError(MyErrorList.TOO_HIGH_NUMBER))(1, 20) )(); expect(isError).toEqual(true); - expect(error).toEqual(new MyError(MyErrorList.TOO_HIGH_NUMBER)); + expect(error).toEqual(myError(MyErrorList.TOO_HIGH_NUMBER)); }); }); }); diff --git a/src/utils/myErrorWrapper.ts b/src/functions/myErrorWrapper.ts similarity index 52% rename from src/utils/myErrorWrapper.ts rename to src/functions/myErrorWrapper.ts index 027e73c..89844b6 100644 --- a/src/utils/myErrorWrapper.ts +++ b/src/functions/myErrorWrapper.ts @@ -1,4 +1,4 @@ -import type { ErrorTypesCatched, arrowFunction } from "@/utils/types"; +import type { ErrorTypesCatched, arrowFunction } from "@/types/internal"; type MyErrorWrapperReturn, errorType> = errorType extends undefined ? [ErrorTypesCatched, true] | [ReturnType, false] @@ -12,6 +12,23 @@ export const myErrorWrapper = (...args: Parameters) => { try { const result = fnThatMayThrow(...args); + + return (errorToThrow ? result : [result, false]) as MyErrorWrapperReturn; + } catch (error) { + if (errorToThrow) throw errorToThrow; + return [error as ErrorTypesCatched, true] as MyErrorWrapperReturn; + } + }; + +// TODO: DO NOT REPEAT CODE +export const myErrorWrapperAsync = + , ErrorType = undefined>( + fnThatMayThrow: T, + errorToThrow?: ErrorType + ): ((...args: Parameters) => Promise>) => + async (...args: Parameters) => { + try { + const result = await fnThatMayThrow(...args); return (errorToThrow ? result : [result, false]) as MyErrorWrapperReturn; } catch (error) { if (errorToThrow) throw errorToThrow; diff --git a/src/index.ts b/src/index.ts index be15524..7fdcbef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,17 @@ -export { MyError, myErrorCatcher, myErrorHandler, myErrorWrapper } from "@/utils"; - -export type { TErrorReturn, TFunctionReturn, TMyError, TDataReturn, TMyErrorList, TMyHandler } from "@/utils/types"; +export * from "@/functions"; +export * from "@/types"; +// export type { +// TMyErrorList, +// IMyError, +// IMyErrorAPI, +// IMyErrorRateLimit, +// IMyErrorValidation, +// TCauseError, +// TDetails, +// TBaseError, +// TBaseErrorExt, +// TValidationError, +// TApiError, +// TApiRateLimit, +// TAllMyErrorTypes +// } from "@/types"; diff --git a/src/tests/componentConcept/componentConcept.test.ts b/src/tests/componentConcept/componentConcept.test.ts deleted file mode 100644 index 9b1ed93..0000000 --- a/src/tests/componentConcept/componentConcept.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -// eslint-disable-next-line @EslintImports/no-deprecated, @EslintImports/namespace -import { expect, it } from "vitest"; - -import { readFile } from "./readFile"; -import { myErrorWrapper } from "@/utils"; -import type { TMyErrorList } from "@/index"; - -it("should read file", () => { - const [result] = readFile("./fileToRead.txt"); - expect(result).toBe("Test pass text"); -}); -it("should Throw Error - not find file", () => { - const [result, error] = readFile("./IDoNotExist.txt"); - if (error) expect(result.code).toBe("FS001"); -}); - -it("Check properly output", () => { - const ErrorList = { - EXECUTING: { - code: "initPackage:EXECUTING_COMMAND", - name: "Executng Command failed", - message: { - user: "Error while executing command", - dev: "execSync has thrown an error while executing the command" - }, - hint: { - user: "Contact with developer to solve the problem", - dev: "Check the command and the error message" - } - }, - NOTSUPPORTEDPKGMNG: { - code: "initPackage:NOT_SUPPORTED_PACKAGE_MANAGER", - name: "The package manager is not Supported", - message: { - user: "The package manager is not supported by the application.", - dev: "Library does not support the package manager" - }, - hint: { - user: "Contact with developer to solve the problem", - dev: "Check library support for the package manager. Create an issue on the library repository with request to add your package manager." - } - } - } as const satisfies TMyErrorList; - - const functionTOP = () => { - function testa(arg: string) { - console.log("TEST", arg); - return arg; - } - const finalPrompt = "Test"; - const [, error] = myErrorWrapper(testa)(finalPrompt); - if (error) return [ErrorList.EXECUTING, true]; - return [void 0, false]; - }; - const result = functionTOP(); - expect(result).toStrictEqual([undefined, false]); -}); diff --git a/src/tests/componentConcept/fileToRead.txt b/src/tests/componentConcept/fileToRead.txt index 8f74e32..28fd1f6 100644 --- a/src/tests/componentConcept/fileToRead.txt +++ b/src/tests/componentConcept/fileToRead.txt @@ -1 +1 @@ -Test pass text \ No newline at end of file +Test file content diff --git a/src/tests/componentConcept/readFile.test.ts b/src/tests/componentConcept/readFile.test.ts new file mode 100644 index 0000000..69b0471 --- /dev/null +++ b/src/tests/componentConcept/readFile.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from "vitest"; +import { ErrorList, readFile } from "./readFile"; +import { join } from "node:path"; +import { myError, myErrorWrapper } from "@/functions"; + +describe("[FUNCTION] readFile", () => { + const testFilePath = join(import.meta.dirname, "/fileToRead.txt"); + const testFileContent = "Test file content\n"; + + it("should successfully read a file", () => { + const result = readFile(testFilePath); + expect(result).toEqual(testFileContent); + }); + + it('should throw a "Not Found" error when file does not exist', () => { + const pathToFile = join(import.meta.dirname, "/oyyoyoyoyoyoyo"); + const [data, isError] = myErrorWrapper(readFile)(pathToFile); + expect(isError).toEqual(true); + expect(data).toEqual(myError(ErrorList.notFound, { hint: [pathToFile] })); + }); + // TODO: Error Test for Corrupted files +}); diff --git a/src/tests/componentConcept/readFile.ts b/src/tests/componentConcept/readFile.ts index 0758fb3..60009da 100644 --- a/src/tests/componentConcept/readFile.ts +++ b/src/tests/componentConcept/readFile.ts @@ -1,16 +1,16 @@ import { readFileSync, existsSync } from "node:fs"; -import { myErrorWrapper } from "@/utils"; -import type { TMyErrorList, TMyHandler } from "@/utils/types"; -import { join } from "node:path"; -import MyError from "@/utils/myError"; +import { myError, myErrorWrapper } from "@/functions"; -const ErrorList = { +import { normalize } from "node:path"; +import type { IMyError, TMyErrorList } from "@/types"; + +export const ErrorList = { notFound: { name: "Not Found", code: "FS001", message: { user: "File not found", dev: "The file you are trying to read does not exist" }, - hint: { user: "Check if the file exists", dev: "Check if the file exists" } - }, + hint: (path: string) => `Check if the file exists at ${path}` + } satisfies IMyError, cantRead: { code: "FS002", name: "Cant Read", @@ -19,24 +19,12 @@ const ErrorList = { user: "Check if the file is not corrupted or permissions", dev: "File is corrupted or has no permissions to be read" } - } + } satisfies IMyError } as const satisfies TMyErrorList; -export const readFileHandler = { - FS001: (name: string) => { - if (name) return [{ code: "123" }, true]; - return ["File not found", false]; - }, - FS002: (name: string) => { - if (name) return [{ code: "123" }, true]; - return [123, false]; - } -} as const satisfies TMyHandler, typeof ErrorList>; - export const readFile = (path: string): string => { - const finalPath = join(import.meta.dirname, path); - if (!existsSync(finalPath)) throw new MyError(ErrorList.notFound); - const [result, error] = myErrorWrapper(readFileSync)(finalPath); - if (error) throw new MyError(ErrorList.cantRead); + const finalPath = normalize(path); + if (!existsSync(finalPath)) throw myError(ErrorList.notFound, { hint: [finalPath] }); + const result = myErrorWrapper(readFileSync, myError(ErrorList.cantRead))(finalPath); return result.toString(); }; diff --git a/src/types/errors.ts b/src/types/errors.ts new file mode 100644 index 0000000..ff54435 --- /dev/null +++ b/src/types/errors.ts @@ -0,0 +1,131 @@ +import type StatusCode from "./statusCodes"; +import type { Prettify } from "@/types/internal"; + +//---------------- +// TYPES ATOMS +//---------------- + +/** + * Represents the severity level of an error or warning. + */ +export type TSeverity = "ERROR" | "WARNING"; + +// eslint-disable-next-line @typescript-eslint/sort-type-constituents +/** + * Represents a more granular severity level. + */ +export type TSeverity2 = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM"; + +export type TErrorMessages = + | string + // eslint-disable-next-line @typescript-eslint/no-explicit-any + | ((...params: any[]) => string); + +/** + * Error Messages to Dev,user (Or Both) + * @example + * ``` + * const Error:{name:string,message:TErrorMessages,hint:TErrorMessages} = { + * name: "Cant Read File", + * message: { + * user: "Can't read file", + * dev: "readFileSync throw error" + * }, + * hint: { + * user: "Check if the file is not corrupted or permissions", + * dev: "File is corrupted or has no permissions to be read" + * } + * } + * ``` + * ``` + * const Error:{name:string,message:TErrorMessages,hint:TErrorMessages} = { + * name: "Not Supported Package manager", + * message: "Package manager is not supported!", + * hint: { + * user: "Contact with Developer to solve problem!", + * dev: "Check package support for Package managers." + * } + * } + * ``` + */ +// export type arrowFunction) => ReturnType> = ( +// ...arguments_: Parameters +// ) => ReturnType; + +export type TErrorMessagesExt = + | Prettify + | { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dev?: TErrorMessages; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + user?: TErrorMessages; + }; + +//---------------- +// Types +//---------------- + +export type TMyErrorList = Record; // TODO +export type TErrorList = { + errors?: T[]; +}; + +/** + * Perfect for throwing catched error by trycatch + */ +export type TCauseError = { + cause?: unknown; +}; +export type TDetails = { + details?: T; +}; +export type TBaseError = { + message?: Parameters[0]; + options?: Parameters[1]; +}; + +export type TBaseErrorExt = { + message?: TErrorMessages; + options?: TCauseError; +}; + +export type TValidationError = { + fields: Array<{ + [K: string]: unknown; + expected_format?: string; + max_value?: number; + message: string; + min_value?: number; + name?: string; + received_value?: unknown; + type?: "custom" | "format" | "range" | "required"; + }>; +}; + +export type TApiError = { + endpoint?: string; + path?: string; + status?: StatusCode | number; + timestamp?: Date; +}; + +export type TApiRateLimit = { + limit?: number; + remaining?: number; + reset?: Date | number; + retryAfter?: Date | number; + status?: StatusCode | number; +}; + +export interface IMyError { + code?: number | string; + hint?: TErrorMessagesExt; + message?: TErrorMessagesExt; + name?: string; +} + +export interface IMyErrorAPI extends IMyError, TApiError {} +export interface IMyErrorRateLimit extends IMyError, TApiRateLimit {} +export interface IMyErrorValidation extends IMyError, TValidationError {} + +export type TAllMyErrorTypes = IMyError | IMyErrorAPI | IMyErrorRateLimit | IMyErrorValidation; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..c07d543 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +export * from "./errors"; +export * from "./statusCodes"; diff --git a/src/utils/types.ts b/src/types/internal.ts similarity index 60% rename from src/utils/types.ts rename to src/types/internal.ts index 61fcdee..e1d22e7 100644 --- a/src/utils/types.ts +++ b/src/types/internal.ts @@ -1,4 +1,13 @@ +import type { IMyError, TMyErrorList } from "@/types"; + +/** + * `any` - But only allowed in development. + * @internal + */ export type TODO = any; // eslint-disable-line @typescript-eslint/no-explicit-any +/** + * @internal + */ export type Prettify = { [K in keyof T]: NonNullable; }; @@ -15,30 +24,23 @@ export type ErrorTypesCatched = | SyntaxErrorConstructor | TypeErrorConstructor; -export type TMyError> = T & { - code?: number | string; - hint?: { - dev?: string; - user?: string; - }; - message?: { - dev?: string; - user?: string; - }; - name?: string; -}; - export type TDataReturn = [T, false]; -export type TErrorReturn> = [TMyError, true]; +export type TErrorReturn> = [CustomError & IMyError, true]; export type TFunctionReturn = Prettify> | Prettify; -export type TMyErrorList> = Record>>; +// export type TMyErrorList> = Record>>; + export type TMyHandler< CustomError, T extends Record & TMyErrorList > = Partial<{ [K in T[keyof T]["code"]]: (...arguments_: K[]) => TFunctionReturn; }>; -// export type TMyHandler<,T extends TMyErrorList> = Partial<{ +// export type TMyHandler> = Partial<{ +// [K in T[keyof T]["code"]]: (...args: K[]) => TFunctionReturn; +// }>; +// export type TMyHandler = Partial<{ // [K in T[keyof T]["code"]]: (...args: K[]) => TFunctionReturn; // }>; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type NoInfer = [T][T extends any ? 0 : never]; diff --git a/src/types/statusCodes.ts b/src/types/statusCodes.ts new file mode 100644 index 0000000..6c78611 --- /dev/null +++ b/src/types/statusCodes.ts @@ -0,0 +1,76 @@ +export enum StatusCode { + // 1xx Informational + Continue = 100, + SwitchingProtocols = 101, + Processing = 102, + EarlyHints = 103, + + // 2xx Success + OK = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultiStatus = 207, + AlreadyReported = 208, + IMUsed = 226, + + // 3xx Redirection + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + SwitchProxy = 306, + TemporaryRedirect = 307, + PermanentRedirect = 308, + + // 4xx Client Errors + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + LengthRequired = 411, + PreconditionFailed = 412, + PayloadTooLarge = 413, + URITooLong = 414, + UnsupportedMediaType = 415, + RangeNotSatisfiable = 416, + ExpectationFailed = 417, + ImATeapot = 418, + MisdirectedRequest = 421, + UnprocessableEntity = 422, + Locked = 423, + FailedDependency = 424, + TooEarly = 425, + UpgradeRequired = 426, + PreconditionRequired = 428, + TooManyRequests = 429, + RequestHeaderFieldsTooLarge = 431, + UnavailableForLegalReasons = 451, + + // 5xx Server Errors + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504, + HTTPVersionNotSupported = 505, + VariantAlsoNegotiates = 506, + InsufficientStorage = 507, + LoopDetected = 508, + NotExtended = 510, + NetworkAuthenticationRequired = 511 +} + +export default StatusCode; diff --git a/src/utils/index.ts b/src/utils/index.ts index cf74b5b..6eb569e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1 @@ -export * from "./myErrorCatcher"; -export * from "./myErrorWrapper"; -export * from "./myErrorHandler"; -export * from "./myError"; +export * from "./isPromise"; diff --git a/src/utils/isPromise.ts b/src/utils/isPromise.ts new file mode 100644 index 0000000..f2baa63 --- /dev/null +++ b/src/utils/isPromise.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line @typescript-eslint/ban-types +export function isPromise(value: Function): Boolean { + return value.constructor.name == "AsyncFuncton"; +} +export default isPromise; diff --git a/src/utils/myError.ts b/src/utils/myError.ts deleted file mode 100644 index 487950b..0000000 --- a/src/utils/myError.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { TMyError } from "./types"; - -// export const MyError = >(error: TMyError): TMyError => error; -export class MyError> { - public code: TMyError["code"]; - public hint: TMyError["hint"]; - public message: TMyError["message"]; - public name: TMyError["name"]; - constructor(error: Required>) { - this.message = error.message; - this.code = error.code; - this.hint = error.hint; - this.name = error.name; - } -} - -export default MyError; diff --git a/src/utils/myErrorHandler.ts b/src/utils/myErrorHandler.ts deleted file mode 100644 index c59f40d..0000000 --- a/src/utils/myErrorHandler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Prettify, TFunctionReturn, TMyErrorList } from "@/utils/types"; -import { myErrorWrapper } from "."; - -const ErrorList = { - noKeyInList: { - code: "EH001", - name: "Not found key in list", - message: { dev: "errorName (key) not found in errorSolutions (object)" }, - hint: { dev: "Check if errorName is in errorSolutions" } - }, - executionError: { - code: "EH002", - name: "Error in Execution", - message: { dev: "Error in execution of Solution" }, - hint: { dev: "Check if the function `errorSolutions[errorName]()` is working properly " } - } -} as const satisfies TMyErrorList; - -export const myErrorHandler = - >(errorCode: T, errorSolutions: K) => - (...args: Parameters): Prettify>> => { - // eslint-disable-next-line @EslintSecurity/detect-object-injection - if (!(errorCode in errorSolutions) && errorSolutions[errorCode]) return [ErrorList.noKeyInList, true]; - try { - // eslint-disable-next-line @EslintSecurity/detect-object-injection - const [data, error] = myErrorWrapper(errorSolutions[errorCode])(...args); - if (error) throw new Error("Error!"); - return data as K[T]; - } catch { - return [ErrorList.executionError, true]; - } - }; -export default myErrorHandler;