vdck
Version:
Vdck is a small, fast, and robust class for type-checking your data in JavaScript/TypeScript, with built-in methods to validate IP and email addresses
224 lines (223 loc) • 9.45 kB
JavaScript
export default class Vdck {
printError;
disabled;
constructor(printError, disabled = false) {
this.printError = printError;
this.disabled = disabled;
}
eLog(message, type) { console.error(`[${new Date().toISOString()}] ${type ? `${type} error:` : "Error:"}`, message); }
;
formatOptions(options) {
const fnObj = {
min: 0,
max: null,
trim: false,
regex: null
};
if (("min" in options) && typeof options.min == "number" && Number.isInteger(options.min) && options.min >= 0)
fnObj.min = options.min;
if (("max" in options) && typeof options.max == "number" && Number.isInteger(options.max) && options.max >= fnObj.min)
fnObj.max = options.max;
if (("trim" in options) && typeof options.trim == "boolean")
fnObj.trim = options.trim;
if (("regex" in options) && options.regex instanceof RegExp)
fnObj.regex = options.regex;
return fnObj;
}
isEmail(value, regex = null) {
if (this.disabled)
return true;
const EMAIL_REGEX = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
if (!(regex instanceof RegExp))
regex = EMAIL_REGEX;
return this.type(value, "string", { min: 5, max: 254, regex: regex });
}
isIP(value) {
if (this.disabled)
return true;
if (!this.type(value, "string", { min: 3, max: 40 }))
return false;
if (value.startsWith("::ffff:")) {
const ipv4Part = value.slice(7);
return this.isIP(ipv4Part);
}
else if (["::1", "::"].includes(value)) {
return true;
}
let splitType = ":";
if (/\./.test(value))
splitType = ".";
const splittedIp = value.split(splitType);
if (splittedIp.length == 4 && splitType == ".") {
for (let i = 0; i < splittedIp.length; i++) {
const triplet = splittedIp[i];
const castTriplet = Number(triplet);
if (isNaN(castTriplet) || !(castTriplet >= 0 && castTriplet <= 255)) {
if (this.printError)
this.eLog(`the given ip was not a valid IPv4 address`, "General");
return false;
}
}
}
else if (splittedIp.length > 1 && splittedIp.length <= 8 && splitType == ":") {
let emptySegments = 0;
for (let i = 0; i < splittedIp.length; i++) {
const part = splittedIp[i];
if (part === "") {
emptySegments++;
continue;
}
if (!/^[0-9a-fA-F]{1,4}$/.test(part)) {
if (this.printError)
this.eLog(`this "${part}" is an invalid IPv6 segment`, "General");
return false;
}
}
if (emptySegments > 1) {
if (this.printError)
this.eLog(`invalid IPv6 address, too many "::"`, "General");
return false;
}
}
else {
if (this.printError)
this.eLog(`the given ip was not a valid ip address`, "General");
return false;
}
return true;
}
type(value, type, options) {
if (this.disabled)
return true;
let correctType = false;
const valueCheck = {}.toString.call(value).slice(8, -1).toLowerCase();
switch (type) {
case "class":
if (valueCheck == "object") {
try {
if ((/^class/.test(value.constructor.toString())))
correctType = true;
}
catch (err) {
if (this.printError)
this.eLog(err, "Unknown");
return false;
}
}
break;
case "number":
case "int":
case "float":
if (valueCheck == "string" || valueCheck == "number") {
if (valueCheck == "string") {
value = Number(value.trim());
if (isNaN(value))
break;
}
if (type == "float" && Number.isInteger(value))
break;
if (type == "int" && !Number.isInteger(value))
break;
correctType = true;
}
break;
case "object":
if (type == valueCheck && !Array.isArray(value)) {
correctType = true;
}
break;
default:
if (type == valueCheck)
correctType = true;
break;
}
if (!correctType) {
if (this.printError)
this.eLog(`the given value is not a valid ${valueCheck}`, "Type");
return false;
}
if (options) {
const fnOptions = this.formatOptions(options);
if (valueCheck === "string") {
if (fnOptions.trim)
value = value.trim();
if (fnOptions.regex instanceof RegExp && !fnOptions.regex.test(value)) {
if (this.printError)
this.eLog("Params error", `the given string doesn't match the regex pattern`);
return false;
}
}
const size = valueCheck === "string" ? value.length :
valueCheck.endsWith("array") ? value.length :
valueCheck === "arraybuffer" ? value.byteLength :
valueCheck === "object" ? Object.keys(value).length :
valueCheck === "map" || valueCheck === "set" ? value.size : null;
if (size !== null) {
if (size < fnOptions.min) {
if (this.printError)
this.eLog("Params error", `the given value has fewer elements/characters than ${fnOptions.min}`);
return false;
}
if (typeof fnOptions.max === "number" && size > fnOptions.max) {
if (this.printError)
this.eLog("Params error", `the given value has more elements/characters than ${fnOptions.max}`);
return false;
}
}
}
return true;
}
sameObjects(main, struct) {
if (this.disabled)
return true;
if (!this.type(main, "object") || !this.type(struct, "object"))
return false;
const mainObjectKeysLength = Object.keys(main).length;
const structObjectKeysLength = Object.keys(struct).length;
if ((mainObjectKeysLength > 0 && structObjectKeysLength == 0) ||
(mainObjectKeysLength == 0 && structObjectKeysLength > 0)) {
return false;
}
try {
for (const key in struct) {
if (Object.prototype.hasOwnProperty.call(struct, key)) {
const expectedType = struct[key];
const actualValue = main[key];
if (!(key in main)) {
if (this.printError)
this.eLog(`'${key}' key is not a main's object key`, "Invalid key");
return false;
}
if (typeof expectedType === "string") {
if (!this.type(actualValue, expectedType.toLowerCase(), undefined)) {
return false;
}
}
else if (typeof expectedType === "object" && !Array.isArray(actualValue) && expectedType !== null) {
if (!this.sameObjects(actualValue, expectedType)) {
if (this.printError)
this.eLog(`key '${key}' does not match the nested structure`, "Mismatched structure");
return false;
}
}
else {
if (this.printError)
this.eLog(`invalid structure definition for key '${key}'`, "Unknown key's type");
return false;
}
}
else {
if (this.printError)
this.eLog(`invalid property in object`, "Invalid property");
return false;
}
}
}
catch (err) {
if (this.printError)
this.eLog(`${String(err)}`, "General");
return false;
}
return true;
}
}