UNPKG

indicative-compiler

Version:

Indicative compiler to compile parsed schema into highly optimized functions

123 lines (122 loc) 4.34 kB
"use strict"; /** * @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;