diff --git a/src/backends/backend.ts b/src/backends/backend.ts index 32b5371a..2995ab51 100644 --- a/src/backends/backend.ts +++ b/src/backends/backend.ts @@ -1,9 +1,9 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Entries, RequiredKeys } from 'utilium'; import { ErrnoError, Errno } from '../error.js'; import type { FileSystem } from '../filesystem.js'; -import { levenshtein } from '../utils.js'; -type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function'; +type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | (abstract new (...args: any[]) => any); /** * Resolves the type of Backend.options from the options interface @@ -104,43 +104,35 @@ export async function checkOptions(backend: T, options: Recor // Check for required options. for (const [optName, opt] of Object.entries(backend.options) as Entries>>) { - const providedValue = options?.[optName]; + const value = options?.[optName]; - if (providedValue === undefined || providedValue === null) { + if (value === undefined || value === null) { if (!opt.required) { continue; } - /* Required option not provided. - if any incorrect options provided, which ones are close to the provided one? - (edit distance 5 === close)*/ - const incorrectOptions = Object.keys(options) - .filter(o => !(o in backend.options)) - .map((a: string) => { - return { str: a, distance: levenshtein(optName, a) }; - }) - .filter(o => o.distance < 5) - .sort((a, b) => a.distance - b.distance); - - throw new ErrnoError( - Errno.EINVAL, - `${backend.name}: Required option '${optName}' not provided.${ - incorrectOptions.length > 0 ? ` You provided '${incorrectOptions[0].str}', did you mean '${optName}'.` : '' - }` - ); + + throw new ErrnoError(Errno.EINVAL, 'Missing required option: ' + optName); } + // Option provided, check type. - const typeMatches = Array.isArray(opt.type) ? opt.type.indexOf(typeof providedValue) != -1 : typeof providedValue == opt.type; - if (!typeMatches) { - throw new ErrnoError( - Errno.EINVAL, - `${backend.name}: Value provided for option ${optName} is not the proper type. Expected ${ - Array.isArray(opt.type) ? `one of {${opt.type.join(', ')}}` : (opt.type as string) - }, but received ${typeof providedValue}` - ); + + type T = typeof opt.type extends (infer U)[] ? U : typeof opt.type; + + const isType = (value: unknown): value is T => (typeof opt.type == 'function' ? value instanceof opt.type : typeof value === opt.type); + + if (Array.isArray(opt.type) ? !opt.type.some(isType) : !isType(value)) { + // The type of the value as a string + const type = typeof value == 'object' && 'constructor' in value ? value.constructor.name : typeof value; + + // The expected type (as a string) + const name = (type: OptionType) => (typeof type == 'function' ? type.name : type); + const expected = Array.isArray(opt.type) ? `one of ${opt.type.map(name).join(', ')}` : name(opt.type as OptionType); + + throw new ErrnoError(Errno.EINVAL, `Incorrect type for "${optName}": ${type} (expected ${expected})`); } if (opt.validator) { - await opt.validator(providedValue); + await opt.validator(value); } // Otherwise: All good! } diff --git a/src/utils.ts b/src/utils.ts index df45b979..9892fcac 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,113 +1,14 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return */ import type * as fs from 'node:fs'; import { randomHex, type ClassLike, type OptionalTuple } from 'utilium'; -import { dirname, resolve, type AbsolutePath } from './emulation/path.js'; +import { resolve, type AbsolutePath } from './emulation/path.js'; import { Errno, ErrnoError } from './error.js'; -import type { FileSystem } from './filesystem.js'; declare global { function atob(data: string): string; function btoa(data: string): string; } -/** - * Synchronous recursive makedir. - * @hidden - */ -export function mkdirpSync(path: string, mode: number, fs: FileSystem): void { - if (!fs.existsSync(path)) { - mkdirpSync(dirname(path), mode, fs); - fs.mkdirSync(path, mode); - } -} - -function _min(d0: number, d1: number, d2: number, bx: number, ay: number): number { - return Math.min(d0 + 1, d1 + 1, d2 + 1, bx === ay ? d1 : d1 + 1); -} - -/** - * Calculates levenshtein distance. - * @hidden - */ -export function levenshtein(a: string, b: string): number { - if (a === b) { - return 0; - } - - if (a.length > b.length) { - [a, b] = [b, a]; // Swap a and b - } - - let la = a.length; - let lb = b.length; - - // Trim common suffix - while (la > 0 && a.charCodeAt(la - 1) === b.charCodeAt(lb - 1)) { - la--; - lb--; - } - - let offset = 0; - - // Trim common prefix - while (offset < la && a.charCodeAt(offset) === b.charCodeAt(offset)) { - offset++; - } - - la -= offset; - lb -= offset; - - if (la === 0 || lb === 1) { - return lb; - } - - const vector = new Array(la << 1); - - for (let y = 0; y < la; ) { - vector[la + y] = a.charCodeAt(offset + y); - vector[y] = ++y; - } - - let x: number; - let d0: number; - let d1: number; - let d2: number; - let d3: number; - for (x = 0; x + 3 < lb; ) { - const bx0 = b.charCodeAt(offset + (d0 = x)); - const bx1 = b.charCodeAt(offset + (d1 = x + 1)); - const bx2 = b.charCodeAt(offset + (d2 = x + 2)); - const bx3 = b.charCodeAt(offset + (d3 = x + 3)); - let dd = (x += 4); - for (let y = 0; y < la; ) { - const ay = vector[la + y]; - const dy = vector[y]; - d0 = _min(dy, d0, d1, bx0, ay); - d1 = _min(d0, d1, d2, bx1, ay); - d2 = _min(d1, d2, d3, bx2, ay); - dd = _min(d2, d3, dd, bx3, ay); - vector[y++] = dd; - d3 = d2; - d2 = d1; - d1 = d0; - d0 = dy; - } - } - - let dd: number = 0; - for (; x < lb; ) { - const bx0 = b.charCodeAt(offset + (d0 = x)); - dd = ++x; - for (let y = 0; y < la; y++) { - const dy = vector[y]; - vector[y] = dd = dy < d0 || dd < d0 ? (dy > dd ? dd + 1 : dy + 1) : bx0 === vector[la + y] ? d0 : d0 + 1; - d0 = dy; - } - } - - return dd; -} - /** * Encodes a string into a buffer * @internal