indicative-compiler
Version:
Indicative compiler to compile parsed schema into highly optimized functions
123 lines (122 loc) • 4.34 kB
JavaScript
/**
* @module compiler/main
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Tree walker is an agnostic implementation to walk over the parsed schema
* tree generated by `indicative-parser`.
*
* The consumer of the code can define a function to consumer the tree nodes and
* define another function to wrap the children of an array node.
*
* ## Why wrap array children?
* Since the length of an array is unknown, until we receive the data at
* runtime, we need a parent function (aka wrapper), that can execute
* the child validations as per the length of the array.
*
* ```js
* function consumerFn (
* field: string,
* rules: ParsedRule[],
* dotPath: string[],
* pointer: string,
* ) {
* }
*
* function arrayWrapper (
* index: string,
* field: string,
* children: ReturnType<consumerFn>[],
* dotPath: string[],
* ) {
* }
*
* new TreeWalker(consumerFn, arrayWrapper).walk(parsedSchema)
* ```
*/
class TreeWalker {
constructor(consumerFn, arrayWrapper) {
this.consumerFn = consumerFn;
this.arrayWrapper = arrayWrapper;
}
/**
* Processes the literal node inside schema tree
*/
processLiteralNode(field, node, dotPath, arrayPath) {
const pointer = arrayPath.concat(dotPath).concat(field).join('.');
return this.consumerFn(field, node.type, node.rules, dotPath, pointer);
}
/**
* Process the object node inside the parsed. All children are parsed
* recursively
*/
processObjectNode(field, node, dotPath, arrayPath) {
let output = [];
/**
* If object itself has rules, then we need to consume that
* as well.
*/
if (node.rules.length) {
const pointer = arrayPath.concat(dotPath).concat(field).join('.');
output.push(this.consumerFn(field, node.type, node.rules, dotPath, pointer));
}
/**
* Walker over object children
*/
output = output.concat(this.walk(node.children, dotPath.concat(field), arrayPath));
return output;
}
/**
* Process the array node of the schema tree. This method will call
* the `arrayWrapper` function and passes all array children to it.
*/
processArrayNode(field, node, dotPath, arrayPath) {
let output = [];
const basePath = arrayPath.concat(dotPath).concat(field);
/**
* If array itself has rules, then we need to process that
* as well
*/
if (node.rules.length) {
const pointer = basePath.join('.');
output.push(this.consumerFn(field, node.type, node.rules, dotPath, pointer));
}
/**
* Processing children for each index. The index of the tree can be a
* wildcard `*`, which means we rely on runtime data to know the
* actual length of the array.
*/
Object.keys(node.each).forEach((index) => {
let child = [];
if (node.each[index].rules.length) {
const pointer = basePath.concat(index).join('.');
child.push(this.consumerFn('::tip::', 'literal', node.each[index].rules, [], pointer));
}
child = child.concat(this.walk(node.each[index].children, [], basePath.concat(index)));
output = output.concat(this.arrayWrapper(index, field, child, dotPath));
});
return output;
}
/**
* Walks the schema tree and invokes the `consumerFn` for each node.
* The output of the consumer is collected and returned back as an
* array.
*/
walk(schema, dotPath = [], arrayPath = []) {
return Object.keys(schema).reduce((result, field) => {
const node = schema[field];
if (node.type === 'literal') {
result = result.concat(this.processLiteralNode(field, node, dotPath, arrayPath));
}
if (node.type === 'object') {
result = result.concat(this.processObjectNode(field, node, dotPath, arrayPath));
}
if (node.type === 'array') {
result = result.concat(this.processArrayNode(field, node, dotPath, arrayPath));
}
return result;
}, []);
}
}
exports.TreeWalker = TreeWalker;
;