UNPKG

alwz

Version:

Extendable library for typecasting

256 lines 8.06 kB
import EV from './ErrorValue.js'; export function isIS(input) { return typeof input === 'function'; } export function assertIS(input) { if (isIS(input)) return true; throw new Converter.InvalidTypeCheckFunction('type check must be a function', input); } export function isFallback(input) { return typeof input === 'function'; } export function assertFallback(input) { if (isFallback(input)) return true; throw new Converter.InvalidFallbackFunction('fallback must be a function', input); } export function isConversion(input) { return typeof input === 'function'; } export function assertConversion(input) { if (isConversion(input)) return true; throw new Converter.InvalidConversionFunction('conversion must be a function', input); } /** * @description converts input data to specific type * - at first checks if conversion is necessary * - attempts conversion based on the input data type * - searches for suitable conversions among registered * - calls a fallback function * * @example <caption>converter creation</caption> * const positive = new Converter( * (input) => typeof input === 'number' && input > 0, * (input) => input === 0 ? 0.1 : 0.2 * ); * * positive * .undefined(() => 0.3) * .boolean((i) => i ? 1 : 0.4) * .number(function(i) { * const result = Math.abs(i) * return this.is(result) ? result : this.fallback(i); * }) * .string((i) => positive.convert(Number(i))) * .symbol((i) => positive.convert(Symbol.keyFor(i))) * .bigint((i) => positive.convert(Number(i))) * .register(Array.isArray, (i) => positive.convert(i[0])) * .register((i) => i === null, (i) => 0.5); * * positive.convert(1); // 1 * positive.convert(0); // 0.1 (fallback) * positive.convert(NaN); // 0.2 (fallback) * positive.convert(undefined); // 0.3 (has own handler) * positive.convert(false); // 0.4 (has own handler) * positive.convert(null); // 0.5 (has own handler) * positive.convert(2n); // 2 * positive.convert(-3); // 3 * positive.convert('4'); // 4 * positive.convert([5, 6]); // 5 * * @example <caption>conversion with prohibited input types</caption> * const converter = new Converter( * (input) => typeof input === 'number', * (input) => { * throw new Error('unknown input data type:' + input); * }) * .string((i) => { * throw new Error('string input is forbidden:' + i); * }) * .boolean(Number) * .register(Array.isArray, (i) => converter.convert(i[0])); * * converter.convert(true); // 1 * converter.convert(2); // 2 * converter.convert('3'); // Error * converter.convert([4]); // 4 * converter.convert(Promise.resolve(5)); // Error */ export class Converter { /** * @param {IS<OUTPUT>} is - initial input data type check (predicate). determines if any conversion is necessary * @param {Fallback<OUTPUT>} fallback - fallback value generator. runs if none of the available conversions are suitable */ constructor(is, fallback) { /** * @description converts data according to saved conversion rules * @param {*} input - input data */ this.convert = (input) => { if (this._is(input)) return input; const type = (typeof input); if (type in this._types) { const conversion = this._types[type]; return conversion.call(this, input); } else { for (const is of this._conversions.keys()) { if (is(input)) { const conversion = this._conversions.get(is); return conversion.call(this, input); } } } return this._fallback(input); }; this.is = is; this.fallback = fallback; this._types = {}; this._conversions = new Map(); Object.seal(this); } get is() { return this._is; } set is(is) { assertIS(is); this._is = is; } get fallback() { return this._fallback; } set fallback(fallback) { assertFallback(fallback); this._fallback = fallback; } get types() { return Object.assign({}, this._types); } get conversions() { return Array.from(this._conversions); } /** * @description adds conversion function for `INPUT` type * @param {IS<INPUT>} is - input data type check (predicate), determines if input can be processed by `conversion` * @param {Conversion<INPUT, OUTPUT>} conversion - `INPUT` to `OUTPUT` conversion function */ register(is, conversion) { assertIS(is); assertConversion(conversion); this._conversions.set(is, conversion); return this; } /** * @description removes conversion for `INPUT` type * @param {IS<INPUT>} is - input type check (predicate) */ unregister(is) { assertIS(is); this._conversions.delete(is); return this; } /** * @description set conversion rule for type `name` if `conversion` is defined or unset if undefined * @param {string} name - one of types (`typeof` result) * @param {Conversion} [conversion] */ type(name, conversion) { if (conversion === undefined) { delete this._types[name]; } else { assertConversion(conversion); this._types[name] = conversion; } return this; } /** * @description conversion rule setter for `undefined` input * @param {Conversion} [conversion] */ undefined(conversion) { return this.type('undefined', conversion); } /** * @description conversion rule setter for `boolean` input * @param {Conversion} [conversion] */ boolean(conversion) { return this.type('boolean', conversion); } /** * @description conversion rule setter for `number` input * @param {Conversion} [conversion] */ number(conversion) { return this.type('number', conversion); } /** * @description conversion rule setter for `bigint` input * @param {Conversion} [conversion] */ bigint(conversion) { return this.type('bigint', conversion); } /** * @description conversion rule setter for `string` input * @param {Conversion} [conversion] */ string(conversion) { return this.type('string', conversion); } /** * @description conversion rule setter for `symbol` input * @param {Conversion} [conversion] */ symbol(conversion) { return this.type('symbol', conversion); } /** * @example * const converter = new Converter( * (i) => typeof i === 'number', * () => 0 * ) * .undefined(() => 1); * * const clone = converter * .clone() * .undefined(() => 2); * * converter.convert(); // 1 * clone.convert(); // 2 */ clone() { return Converter.build(this._is, this._fallback, this._types, this._conversions); } static build(is, fallback, types = {}, conversions = []) { const converter = new Converter(is, fallback); for (const type in types) { converter.type(type, types[type]); } for (const [is, conversion] of conversions) { converter.register(is, conversion); } return converter; } static assert(input) { if (Converter.is(input)) return true; throw new Converter.InvalidConverter('input is not a Converter', input); } } Converter.InvalidTypeCheckFunction = class extends EV { }; Converter.InvalidFallbackFunction = class extends EV { }; Converter.InvalidConversionFunction = class extends EV { }; Converter.InvalidConverter = class extends EV { }; Converter.is = (input) => input instanceof Converter; export default Converter; //# sourceMappingURL=Converter.js.map