telefunc
Version:
Remote functions. Instead of API.
292 lines (291 loc) • 10.8 kB
JavaScript
export { shield };
export { shieldIsMissing };
export { shieldApply };
export { shieldToHumandReadable };
import { isPlainObject, unique, isCallable, assert, assertUsage } from '../utils.js';
const shieldKey = '__telefunc_shield';
const isVerifierKey = '__telefunc_isVerifier';
const isVerifierTupleKey = '__telefunc_isVerifierTuple';
const shield = function (arg1, arg2, options) {
let telefunction;
let telefunctionShield;
if (isTelefunction(arg1)) {
assertShield(arg2, 'shield(telefunction, telefunctionShield)');
telefunction = arg1;
telefunctionShield = arg2;
}
else if (isTelefunction(arg2)) {
assertShield(arg1, 'shield(telefunctionShield, telefunction)');
telefunction = arg2;
telefunctionShield = arg1;
}
else {
assertUsage(false, '[shield(arg1, arg2)] Neither `arg1` nor `arg2` is a function, but one should be.');
}
installShield(telefunction, telefunctionShield, !!(options === null || options === void 0 ? void 0 : options.__autoGenerated));
return telefunction;
};
function assertShield(telefunctionShield, shieldInvocation) {
assertUsage(Array.isArray(telefunctionShield) || isVerifierTuple(telefunctionShield), [
`[${shieldInvocation.replace('telefunctionShield', 'args')}]`,
'`args` should be an array.',
`Example of correct usage: \`${shieldInvocation.replace('telefunctionShield', '[shield.type.string]')}\`.`,
`Example of wrong usage: \`${shieldInvocation.replace('telefunctionShield', 'shield.type.string')}\`.`,
].join(' '));
}
function isTelefunction(thing) {
return isCallable(thing) && !isVerifier(thing);
}
function installShield(telefunction, telefunctionShield, generated) {
const installedShield = telefunction[shieldKey];
if (installedShield && generated) {
// another shield is already installed, so not installing generated shield
return;
}
;
telefunction[shieldKey] = telefunctionShield;
}
function shieldIsMissing(telefunction) {
const telefunctionShield = getTelefunctionShield(telefunction);
return telefunctionShield === null;
}
function shieldApply(telefunction, args) {
const telefunctionShield = getTelefunctionShield(telefunction);
assert(telefunctionShield !== null);
return verifyOuter(telefunctionShield, args);
}
function getTelefunctionShield(telefunction) {
return telefunction[shieldKey] || null;
}
function shieldToHumandReadable(telefunctionShield) {
return StringBetter(telefunctionShield);
}
// Like `String()` but with support for objects and arrays
function StringBetter(thing) {
if (isPlainObject(thing)) {
let str = '';
const entries = Object.entries(thing);
entries.forEach(([key, val], i) => {
str += `${String(key)}:${StringBetter(val)}`;
const isLast = i === entries.length - 1;
if (!isLast) {
str += ',';
}
});
str = `{${str}}`;
return str;
}
if (Array.isArray(thing)) {
return `[${thing.map((el) => StringBetter(el)).join(',')}]`;
}
return String(thing);
}
function verifyOuter(verifier, args) {
assert(Array.isArray(args));
if (Array.isArray(verifier)) {
verifier = shield.type.tuple(...verifier);
assert(isVerifierTuple(verifier));
}
if (isVerifierTuple(verifier)) {
return verifyRecursive(verifier, args, '[root]');
}
assert(false);
}
function verifyRecursive(verifier, arg, breadcrumbs) {
assert(breadcrumbs.startsWith('[root]'));
if (isVerifier(verifier)) {
return verifier(arg, breadcrumbs);
}
if (isPlainObject(verifier)) {
const obj = verifier;
return verifyObject(obj, arg, breadcrumbs);
}
if (verifier === undefined) {
return shield.type.const(undefined)(arg, breadcrumbs);
}
const errorPrefix = `[shield()] Bad shield definition: ${breadcrumbs}`;
const errorSuffix = `See https://telefunc.com/shield`;
assertUsage(!Array.isArray(verifier), errorPrefix +
' is a plain JavaScript array which is forbidden: use `shield.type.tuple()` instead of `[]`. ' +
errorSuffix);
assertUsage(false, `${errorPrefix} is \`${getTypeName(verifier)}\` which is forbidden. Always use \`shield.type[x]\` or a plain JavaScript Object. ${errorSuffix}`);
}
function verifyObject(obj, arg, breadcrumbs) {
if (!isPlainObject(arg)) {
return errorMessage(breadcrumbs, getTypeName(arg), 'object');
}
for (const key of unique([...Object.keys(obj), ...Object.keys(arg)])) {
const res = verifyRecursive(obj[key], arg[key], `${breadcrumbs} > [object: value of key \`${key}\`]`);
if (res !== true) {
return res;
}
}
return true;
}
const type = (() => {
const or = (...elements) => {
const verifier = (input, breadcrumbs) => {
const typeTargets = elements.map((el) => verifyRecursive(el, input, `${breadcrumbs}`));
if (typeTargets.includes(true)) {
return true;
}
return `${breadcrumbs} is of wrong type`;
};
markVerifier(verifier);
verifier.toString = () => elements.map((el) => StringBetter(el)).join('|');
return verifier;
};
const tuple = (...elements) => {
const verifier = (input, breadcrumbs) => {
if (!Array.isArray(input)) {
return errorMessage(breadcrumbs, getTypeName(input), 'tuple');
}
const errorMessages = [...Array(Math.max(input.length, elements.length)).keys()]
.map((i) => verifyRecursive(i > elements.length - 1 ? type.const(undefined) : elements[i], input[i], `${breadcrumbs} > [tuple: element ${i}]`))
.filter((res) => res !== true);
if (errorMessages.length === 0) {
return true;
}
return errorMessages[0];
};
markVerifier(verifier);
markVerifierTuple(verifier);
verifier.toString = () => StringBetter(elements);
return verifier;
};
const array = (arrayType) => {
const verifier = (input, breadcrumbs) => {
if (!Array.isArray(input)) {
return errorMessage(breadcrumbs, getTypeName(input), 'array');
}
const errorMessages = input
.map((_, i) => verifyRecursive(arrayType, input[i], `${breadcrumbs} > [array element ${i}]`))
.filter((res) => res !== true);
if (errorMessages.length === 0) {
return true;
}
return errorMessages[0];
};
markVerifier(verifier);
verifier.toString = () => {
let s = StringBetter(arrayType);
if (s.includes(',')) {
s = `(${s})`;
}
s = `${s}[]`;
return s;
};
return verifier;
};
const object = (objectType) => {
const verifier = (input, breadcrumbs) => {
if (typeof input !== 'object' || input === null || input.constructor !== Object) {
return errorMessage(breadcrumbs, getTypeName(input), 'object');
}
const errorMessages = Object.entries(input)
.map(([key, val]) => verifyRecursive(objectType, val, `${breadcrumbs} > [object: value of key \`${key}\`]`))
.filter((res) => res !== true);
if (errorMessages.length === 0) {
return true;
}
return errorMessages[0];
};
markVerifier(verifier);
verifier.toString = () => {
let s = StringBetter(objectType);
if (s.includes(',')) {
s = `(${s})`;
}
s = `Record<string, ${s}}`;
return s;
};
return verifier;
};
const const_ = (val) => {
const verifier = (input, breadcrumbs) => input === val ? true : errorMessage(breadcrumbs, String(input), String(val));
markVerifier(verifier);
verifier.toString = () => StringBetter(val);
return verifier;
};
const string = (() => {
const verifier = (input, breadcrumbs) => typeof input === 'string' ? true : errorMessage(breadcrumbs, getTypeName(input), 'string');
markVerifier(verifier);
verifier.toString = () => 'string';
return verifier;
})();
const number = (() => {
const verifier = (input, breadcrumbs) => typeof input === 'number' ? true : errorMessage(breadcrumbs, getTypeName(input), 'number');
markVerifier(verifier);
verifier.toString = () => 'number';
return verifier;
})();
const boolean = (() => {
const verifier = (input, breadcrumbs) => input === true || input === false ? true : errorMessage(breadcrumbs, getTypeName(input), 'boolean');
markVerifier(verifier);
verifier.toString = () => 'boolean';
return verifier;
})();
const date = (() => {
const verifier = (input, breadcrumbs) => typeof input === 'object' && input !== null && input.constructor === Date
? true
: errorMessage(breadcrumbs, getTypeName(input), 'date');
markVerifier(verifier);
verifier.toString = () => 'date';
return verifier;
})();
const any = (() => {
const verifier = () => true;
markVerifier(verifier);
verifier.toString = () => 'date';
return verifier;
})();
return {
string,
number,
boolean,
date,
array,
object,
or,
tuple,
const: const_,
optional: (param) => or(param, const_(undefined)),
nullable: (param) => or(param, const_(null)),
any,
};
})();
shield.type = type;
function errorMessage(breadcrumbs, is, should) {
return `${breadcrumbs} is \`${is}\` but should be \`${should}\`.`;
}
function isVerifier(thing) {
return thing && thing[isVerifierKey] === true;
}
function markVerifier(verifier) {
assert(isCallable(verifier));
verifier[isVerifierKey] = true;
}
function isVerifierTuple(thing) {
return isVerifier(thing) && thing[isVerifierTupleKey] === true;
}
function markVerifierTuple(verifier) {
verifier[isVerifierTupleKey] = true;
}
function getTypeName(thing) {
if (thing === null) {
return 'null';
}
if (thing === undefined) {
return 'undefined';
}
if (typeof thing === 'object') {
assert(thing !== null);
if (thing.constructor === Date) {
return 'date';
}
if (Array.isArray(thing)) {
return 'array';
}
}
return typeof thing;
}