From 36feb3b40711f6e39339d4e222a1b467e1ccfb77 Mon Sep 17 00:00:00 2001 From: RichardDorian Date: Wed, 19 Oct 2022 21:44:02 +0200 Subject: [PATCH] feat: :sparkles: add validation functions --- types/algorithms.d.ts | 4 +- types/structures.d.ts | 32 +++++++------- types/utilities.d.ts | 10 ++--- types/validation.d.ts | 69 +++++++++++++++++++++++++++++++ utilities/generateRandomNumber.js | 8 ++++ utilities/readJSONFile.js | 6 +++ utilities/wait.js | 4 ++ validation/index.js | 2 + validation/primitives.js | 18 ++++++++ validation/validatePrimitive.js | 30 ++++++++++++++ 10 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 validation/primitives.js create mode 100644 validation/validatePrimitive.js diff --git a/types/algorithms.d.ts b/types/algorithms.d.ts index 711660a..16d546a 100644 --- a/types/algorithms.d.ts +++ b/types/algorithms.d.ts @@ -27,7 +27,7 @@ declare module 'nodejs-vitals/algorithms' { * binarySearch(arr, 'c'); // 2 * binarySearch(arr, 'z'); // -1 * ``` - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ export function binarySearch(array: T[], key: T): number; /** @@ -50,7 +50,7 @@ declare module 'nodejs-vitals/algorithms' { * const arr = [ 'b', 'y', 'q', 'k' ]; * selectionSort(arr); // [ 'b', 'k', 'q', 'y' ] * ``` - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ export function selectionSort(array: T[]): T[]; } diff --git a/types/structures.d.ts b/types/structures.d.ts index 04cdff6..6d734fe 100644 --- a/types/structures.d.ts +++ b/types/structures.d.ts @@ -22,37 +22,37 @@ declare module 'nodejs-vitals/structures' { * // If you are using TypeScript you can use the type parameter to specify the type of the queue * const typedQueue = new Queue(); * ``` - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public constructor(maxSize?: number); /** * Push a value at the end of the queue * @param value Value to add to the queue - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public enqueue(value: T): void; /** * Removes and returns the first item from the queue * @returns The item removed from the queue - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public dequeue(): T; /** * Returns true if the queue is empty, false otherwise * @returns True if the queue is empty, false otherwise - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public isEmpty(): boolean; /** * Returns true if the queue is full, false otherwise * @returns True if the queue is full, false otherwise - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public isFull(): boolean; } /** * Node class for the Queue - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ export class QueueNode { /** Value stored in the node */ @@ -62,13 +62,13 @@ declare module 'nodejs-vitals/structures' { /** * Instantiate a new QueueNode * @param value Value to store in the node - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public constructor(value: T); } /** * Stack data structure - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ export class Stack { /** Number of items currently in the stack */ @@ -90,44 +90,44 @@ declare module 'nodejs-vitals/structures' { * * // If you are using TypeScript you can use the type parameter to specify the type of the stack * const typedStack = new Queue(); - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` * ``` */ public constructor(maxSize?: number); /** * Push a value at the top of the stack * @param value Value to push onto the stack - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public push(value: T): void; /** * Removes and returns the top item from the stack * @returns The item removed from the stack - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public pop(): T; /** * Returns the top item from the stack without removing it * @returns The top item from the stack - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public peek(): T; /** * Returns true if the stack is empty, false otherwise * @returns True if the stack is empty, false otherwise - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public isEmpty(): boolean; /** * Returns true if the stack is full, false otherwise * @returns True if the stack is full, false otherwise - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public isFull(): boolean; } /** * Node class for the Stack - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ export class StackNode { /** Value stored in the node */ @@ -137,7 +137,7 @@ declare module 'nodejs-vitals/structures' { /** * Instantiate a new StackNode * @param value Value to store in the node - * @since `nodejs-utilities@1.0.0` + * @since `nodejs-vitals@1.0.0` */ public constructor(value: T); } diff --git a/types/utilities.d.ts b/types/utilities.d.ts index 0764b63..9303325 100644 --- a/types/utilities.d.ts +++ b/types/utilities.d.ts @@ -11,7 +11,7 @@ declare module 'nodejs-vitals/utilities' { * generateRandomNumberE(0, 10); // 3 * generateRandomNumberE(0, 10); // 6 * ``` - * @since `nodejs-utilities@1.0.1` + * @since `nodejs-vitals@1.0.1` */ export function generateRandomNumberE(min: number, max: number): number; /** @@ -26,7 +26,7 @@ declare module 'nodejs-vitals/utilities' { * generateRandomNumberI(0, 10); // 10 * generateRandomNumberI(0, 10); // 5 * ``` - * @since `nodejs-utilities@1.0.1` + * @since `nodejs-vitals@1.0.1` */ export function generateRandomNumberI(min: number, max: number): number; /** @@ -34,7 +34,7 @@ declare module 'nodejs-vitals/utilities' { * file path synchronously * @param path A path to a file * @returns An object containing the parsed file - * @since `nodejs-utilities@1.0.1` + * @since `nodejs-vitals@1.0.1` */ export function readJSONFileSync(path: string): T; /** @@ -42,7 +42,7 @@ declare module 'nodejs-vitals/utilities' { * file path asynchronously * @param path A path to a file * @returns An object containing the parsed file - * @since `nodejs-utilities@1.0.1` + * @since `nodejs-vitals@1.0.1` */ export function readJSONFile(path: string): Promise; /** @@ -61,7 +61,7 @@ declare module 'nodejs-vitals/utilities' { * await wait(10 * 1000); * console.log('Bar, printed 10 seconds after the first log') * ``` - * @since `nodejs-utilities@1.0.1` + * @since `nodejs-vitals@1.0.1` */ export function wait(ms: number): Promise; } diff --git a/types/validation.d.ts b/types/validation.d.ts index 038414d..fdbaa7e 100644 --- a/types/validation.d.ts +++ b/types/validation.d.ts @@ -10,8 +10,77 @@ type Types = { }; declare module 'nodejs-vitals/validation' { + /** + * Checks whether or not all the items in the + * given array are type of the given type + * @param type Type to check the items in the array for + * @param arr The array to check + * @returns Whether or not all the items are + * type of the given type + * @example + * ```javascript + * const arr = [ 2, 4, 'Hello World!', 8 ]; + * isArrayOf('number', arr); // false + * ``` + * @example + * ```javascript + * const arr = [ 2, 4, 8, 12 ]; + * isArrayOf('number', arr); // true + * ``` + * @since `nodejs-vitals@1.0.1` + */ export function isArrayOf( type: T, arr: unknown ): arr is Types[T][]; + /** + * @enum Primitives in NodeJS + * @since `nodejs-vitals@1.0.1` + */ + export enum Primitives { + BIGINT = 'bigint', + BOOLEAN = 'boolean', + FUNCTION = 'function', + NUMBER = 'number', + OBJECT = 'object', + STRING = 'string', + SYMBOL = 'symbol', + UNDEFINED = 'undefined', + } + /** + * Checks if the given string is + * the name of a primitive + * @param value Value to check for + * @returns Whether or not the given string is the name of a primitive + * @example + * ```javascript + * isPrimitive('bigint'); // true + * isPrimitive('number'); // true + * isPrimitive('Hello World!'); // false + * ``` + * @since `nodejs-vitals@1.0.1` + */ + export function isPrimitive(value: unknown): value is keyof Types; + /** + * Throws an error if the type of given value does + * not match the name of the given primitive + * @param value Value to check + * @param type Name of the primitive + * @param argumentName Optional, the argument name, displayed in the error message + * @example + * ```javascript + * function add(a, b) { + * validatePrimitive(a, 'number', 'a'); // Throws an error if a + * validatePrimitive(b, 'number', 'b'); // or b is not a number + * + * return a + b; + * } + * ``` + * @since `nodejs-vitals@1.0.1` + */ + export function validatePrimitive( + value: unknown, + type: keyof Types, + argumentName?: string + ): void; } diff --git a/utilities/generateRandomNumber.js b/utilities/generateRandomNumber.js index b9fef33..1ba601a 100644 --- a/utilities/generateRandomNumber.js +++ b/utilities/generateRandomNumber.js @@ -1,8 +1,16 @@ +const { validatePrimitive } = require('../validation'); + function generateRandomNumberE(min, max) { + validatePrimitive(min, 'number', 'min'); + validatePrimitive(max, 'number', 'max'); + return Math.floor(Math.random() * (max - min) + min); } function generateRandomNumberI(min, max) { + validatePrimitive(min, 'number', 'min'); + validatePrimitive(max, 'number', 'max'); + return Math.floor(Math.random() * (max - min + 1) + min); } diff --git a/utilities/readJSONFile.js b/utilities/readJSONFile.js index 2b003c7..e753f7c 100644 --- a/utilities/readJSONFile.js +++ b/utilities/readJSONFile.js @@ -1,12 +1,18 @@ const { readFileSync } = require('fs'); const { readFile } = require('fs/promises'); +const { validatePrimitive } = require('../validation'); + function readJSONFileSync(path) { + validatePrimitive(path, 'string', 'path'); + const raw = readFileSync(path, 'utf8'); return JSON.parse(raw); } async function readJSONFile(path) { + validatePrimitive(path, 'string', 'path'); + const raw = await readFile(path, 'utf8'); return JSON.parse(raw); } diff --git a/utilities/wait.js b/utilities/wait.js index 1c96411..7ba7103 100644 --- a/utilities/wait.js +++ b/utilities/wait.js @@ -1,3 +1,7 @@ +const { validatePrimitive } = require('../validation'); + module.exports = async function wait(ms) { + validatePrimitive(ms, 'number', 'ms'); + return new Promise((resolve) => setTimeout(resolve, ms)); }; diff --git a/validation/index.js b/validation/index.js index 44a2cb9..78cb326 100644 --- a/validation/index.js +++ b/validation/index.js @@ -1,3 +1,5 @@ module.exports = { isArrayOf: require('./isArrayOf'), + ...require('./primitives'), + validatePrimitive: require('./validatePrimitive'), }; diff --git a/validation/primitives.js b/validation/primitives.js new file mode 100644 index 0000000..2d95673 --- /dev/null +++ b/validation/primitives.js @@ -0,0 +1,18 @@ +const Primitives = Object.freeze({ + BIGINT: 'bigint', + BOOLEAN: 'boolean', + FUNCTION: 'function', + NUMBER: 'number', + OBJECT: 'object', + STRING: 'string', + SYMBOL: 'symbol', + UNDEFINED: 'undefined', +}); + +const primitives = Object.values(Primitives); + +function isPrimitive(value) { + return primitives.includes(value); +} + +module.exports = { Primitives, isPrimitive }; diff --git a/validation/validatePrimitive.js b/validation/validatePrimitive.js new file mode 100644 index 0000000..9150e9e --- /dev/null +++ b/validation/validatePrimitive.js @@ -0,0 +1,30 @@ +const { isPrimitive } = require('./primitives'); + +module.exports = function validatePrimitive(value, type, argumentName) { + if (!isPrimitive(type)) + throw new TypeError( + `The "type" argument must be a string representing a primitive. Received ${type}` + ); + + if (typeof value === type) return; + + argumentName = argumentName + ? `The "${argumentName}" argument` + : 'The argument'; + + let actualType = typeof value; + let primitive = true; + if (actualType === 'object') + try { + actualType = Object.getPrototypeOf(value).constructor.name; + primitive = false; + } catch (error) {} + + const error = new TypeError( + `${argumentName} must be of type ${type}. Received ${ + primitive ? 'a type of' : 'an instance of' + } ${actualType}` + ); + error.code = 'ERR_INVALID_ARG_TYPE'; + throw error; +};