@quenk/preconditions
Version:
Make data satisfy constraints before using.
184 lines • 6.65 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = exports.defaultBuiltins = exports.DEFAULT_PIPELINE_KEY = void 0;
const except_1 = require("@quenk/noni/lib/control/except");
const array_1 = require("@quenk/noni/lib/data/array");
const either_1 = require("@quenk/noni/lib/data/either");
const record_1 = require("@quenk/noni/lib/data/record");
const type_1 = require("@quenk/noni/lib/data/type");
exports.DEFAULT_PIPELINE_KEY = 'preconditions';
/**
* defaultBuiltins available.
*/
exports.defaultBuiltins = {
object: ['base.default', 'base.const', 'base.type'],
array: [
'base.default',
'base.const',
'base.type',
'array.nonEmpty',
'array.minItems',
'array.maxItems'
],
string: [
'base.default',
'base.cast',
'base.const',
'base.type',
'base.enum',
'string.nonEmpty',
'string.minLength',
'string.maxLength',
'string.pattern',
'string.trim',
'string.lowerCase',
'string.upperCase',
'string.split'
],
number: [
'base.default',
'base.cast',
'base.const',
'base.type',
'base.enum',
'number.min',
'number.max'
],
boolean: [
'base.default',
'base.cast',
'base.const',
'base.type',
'base.enum'
]
};
const booleanExtractors = ['trim', 'lowerCase', 'upperCase', 'cast'];
const typeTakers = ['cast'];
/**
* parse a Schema into a representation.
*
* This function works by converting a schema into some generic representation
* T. It walks the properties of an object type schema or the items property
* of an array schema in a stack safe manner.
*
* The provided ParseContext is given the chance to transform each encountered
* schema into a precondition via visit().
*
* @param ctx - The parse context.
* @param schema - The root schema to parse.
*/
const parse = (ctx, schema) => {
let result = [];
let initItem = [schema, 0, result];
let initFrame = [[initItem]];
let pending = [initFrame];
PENDING: while (!(0, array_1.empty)(pending)) {
let [stack, owner] = pending.pop();
while (!(0, array_1.empty)(stack)) {
let [currentSchema, currentPath, currentTarget] = (stack.pop());
let builtins = currentSchema.readOnly
? []
: takeBuiltins((0, record_1.merge)(exports.defaultBuiltins, ctx.builtinsAvailable || {}), currentSchema);
let preconditions = ctx.getPipeline(currentSchema);
if (currentSchema.readOnly && (0, array_1.empty)(preconditions))
continue;
if (!isComplex(currentSchema)) {
let eprecs = toPrecondition(ctx, [
...builtins,
...preconditions
]);
if (eprecs.isLeft())
return (0, except_1.raise)(eprecs.takeLeft());
currentTarget[currentPath] = ctx.visit([
currentSchema.type,
eprecs.takeRight(),
Boolean(currentSchema.optional)
]);
}
else {
pending.push([stack, owner]); // Save current state for later.
let ebuiltinPrecs = toPrecondition(ctx, builtins);
if (ebuiltinPrecs.isLeft())
return (0, except_1.raise)(ebuiltinPrecs.takeLeft());
let eprecs = toPrecondition(ctx, preconditions);
if (eprecs.isLeft())
return (0, except_1.raise)(eprecs.takeLeft());
let builtinPrecs = ebuiltinPrecs.takeRight();
let precs = eprecs.takeRight();
if (currentSchema.type === 'object') {
let schema = currentSchema;
let newStack = [];
let object = [
'object',
[builtinPrecs, {}, undefined, precs],
Boolean(schema.optional)
];
let [, args] = object;
for (let [key, prop] of Object.entries(schema.properties || {}))
newStack.push([prop, key, args[1]]);
if ((0, type_1.isObject)(schema.additionalProperties))
newStack.push([
schema.additionalProperties,
2,
args
]);
pending.push([
newStack,
[object, currentPath, currentTarget]
]);
}
else if (currentSchema.type === 'array') {
let schema = currentSchema;
let array = [
'array',
[builtinPrecs, null, precs],
Boolean(schema.optional)
];
let newStack = [];
if (schema.items)
newStack.push([schema.items, 1, array[1]]);
pending.push([
newStack,
[array, currentPath, currentTarget]
]);
}
continue PENDING;
}
}
if (owner) {
let [val, loc, target] = owner;
target[loc] = ctx.visit(val);
}
}
return (0, either_1.right)(result.pop());
};
exports.parse = parse;
const takeBuiltins = (available, schema) => (available[schema.type] || []).reduce((result, path) => {
let [, name] = path.split('.');
if (Object.prototype.hasOwnProperty.call(schema, name)) {
if (booleanExtractors.includes(name) &&
schema[name] !== false) {
result.push([
path,
typeTakers.includes(name) ? [schema.type] : []
]);
}
else {
result.push([path, [schema[name]]]);
}
}
return result;
}, []);
const toPrecondition = (ctx, list) => {
let result = [];
for (let spec of list) {
let mprec = ctx.get(spec);
if (mprec.isNothing())
return (0, except_1.raise)(`Could not resolve the following value to a precondition: ${spec} !`);
result.push(mprec.get());
}
return (0, either_1.right)(result);
};
const complex = ['object', 'array'];
const isComplex = (schema) => complex.includes(schema.type);
//# sourceMappingURL=parse.js.map