@travetto/transformer
Version:
Functionality for AST transformations, with transformer registration, and general utils
113 lines (109 loc) • 3.87 kB
text/typescript
const REGEX_PAT = /[\/](.*)[\/](i|g|m|s)?/;
export class CoerceUtil {
/**
* Is a value a plain JS object, created using {}
*/
static #isPlainObject(obj: unknown): obj is Record<string, unknown> {
return typeof obj === 'object' // separate from primitives
&& obj !== undefined
&& obj !== null // is obvious
&& obj.constructor === Object // separate instances (Array, DOM, ...)
&& Object.prototype.toString.call(obj) === '[object Object]'; // separate build-in like Math
}
/**
* Create regex from string, including flags
*/
static #toRegex(input: string | RegExp): RegExp {
if (input instanceof RegExp) {
return input;
} else if (REGEX_PAT.test(input)) {
const [, pat, mod] = input.match(REGEX_PAT) ?? [];
return new RegExp(pat, mod);
} else {
return new RegExp(input);
}
}
/**
* Coerce an input of any type to the class provided
* @param input Input value
* @param type Class to coerce to (String, Boolean, Number, Date, RegEx, Object)
* @param strict Should a failure to coerce throw an error?
*/
static coerce(input: unknown, type: typeof String, strict?: boolean): string;
static coerce(input: unknown, type: typeof BigInt, strict?: boolean): bigint;
static coerce(input: unknown, type: typeof Number, strict?: boolean): number;
static coerce(input: unknown, type: typeof Boolean, strict?: boolean): boolean;
static coerce(input: unknown, type: typeof Date, strict?: boolean): Date;
static coerce(input: unknown, type: typeof RegExp, strict?: boolean): RegExp;
static coerce(input: unknown, type: Function, strict = true): unknown {
// Do nothing
if (input === null || input === undefined) {
return input;
} else if (!strict && type !== String && input === '') {
return undefined; // treat empty string as undefined for non-strings in non-strict mode
} else if (type && input instanceof type) {
return input;
}
switch (type) {
case Date: {
const value = typeof input === 'number' || /^[-]?\d+$/.test(`${input}`) ?
new Date(parseInt(`${input}`, 10)) : new Date(`${input}`);
if (strict && Number.isNaN(value.getTime())) {
throw new Error(`Invalid date value: ${input}`);
}
return value;
}
case Number: {
const value = `${input}`.includes('.') ? parseFloat(`${input}`) : parseInt(`${input}`, 10);
if (strict && Number.isNaN(value)) {
throw new Error(`Invalid numeric value: ${input}`);
}
return value;
}
case BigInt: {
try {
return BigInt(typeof input === 'string' || typeof input === 'number' ? input : `${input}`);
} catch {
if (strict) {
throw new Error(`Invalid numeric value: ${input}`);
}
return;
}
}
case Boolean: {
const match = `${input}`.match(/^((?<TRUE>true|yes|1|on)|false|no|off|0)$/i);
if (strict && !match) {
throw new Error(`Invalid boolean value: ${input}`);
}
return !!match?.groups?.TRUE;
}
case RegExp: {
if (typeof input === 'string') {
try {
return this.#toRegex(input);
} catch {
if (strict) {
throw new Error(`Invalid regex: ${input}`);
} else {
return;
}
}
} else if (strict) {
throw new Error('Invalid regex type');
} else {
return;
}
}
case Object: {
if (!strict || this.#isPlainObject(input)) {
return input;
} else {
throw new Error('Invalid object type');
}
}
case undefined:
case String: return `${input}`;
}
throw new Error(`Unknown type ${type.name}`);
}
}