UNPKG

indicative-parser

Version:
242 lines (241 loc) 7.18 kB
"use strict"; /* * indicative-parser * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const haye_pipe_1 = __importDefault(require("haye/dist/haye-pipe")); const haye_array_presenter_1 = __importDefault(require("haye/dist/haye-array-presenter")); function toCamelCase(ruleName) { return ruleName.replace(/_(\w)/g, (_match, group) => group.toUpperCase()); } /** * Updates rules on the given node. If node is missing, then a literal node is * created automatically. Literal nodes can later transform into `object` and * `array` nodes. */ function setLiteral(source, key, rules) { const item = (source[key] || { type: 'literal' }); item.rules = rules; source[key] = item; return item; } /** * Creates/updates literal node to an object node. Since `object` node * properties are different from `literal` node, we need to set those * properties (if missing). * * If node already exists and is an array node, then this method will raise an * exception */ function setObject(source, key) { if (source[key] && source[key].type === 'array') { throw new Error(`cannot reshape ${key} array to an object`); } const item = (source[key] || { rules: [] }); item.type = 'object'; item.children = item.children || {}; source[key] = item; return item; } /** * Creates/updates literal node to an array node. Since `array` node * properties are different from `literal` node, we need to set those * properties (if missing). * * If node already exists and is an object node, then this method will raise an * exception */ function setArray(source, key, index) { if (source[key] && source[key].type === 'object') { throw new Error(`cannot reshape ${key} object to an array`); } const item = (source[key] || { rules: [] }); item.each = item.each || {}; item.each[index] = item.each[index] || { children: {}, rules: [] }; item.type = 'array'; source[key] = item; return item; } /** * Parses field tokens recursively to a [[ParsedSchema]] tree */ function parseFieldForRules(tokens, rules, out, index = 0) { const token = tokens[index++]; /** * Finding if we are on the last item. Last item defines * the rules for the current node inside the tree */ const isLast = tokens.length === index; /** * Indexed array have `digits` like `users.0.username` */ const isIndexedArray = /^\d+$/.test(tokens[index]); /** * Is upcoming token an array */ const isArray = tokens[index] === '*' || isIndexedArray; /** * Last item was marked as array, since current token is a `*` * or has defined index */ if (token === '*' || /^\d+$/.test(token)) { /** * Last item must update rules for each item for the array */ if (isLast) { out.each[token].rules = rules; return; } /** * Nested arrays */ if (isArray) { /** * The code after the error works fine. However, in order to support * 2d arrays, we need to implement them inside the declarative * schema and compiler as well. * * For now, it's okay to skip this feature and later work on it * across all the modules. */ throw new Error('2d arrays are currently not supported'); // const item = setArray( // (out as SchemaNodeArray).each[token].children, // token, // isIndexedArray ? tokens[index] : '*', // ) // return parseFieldForRules(tokens, rules, item, index) } /** * Otherwise continue recursion */ return parseFieldForRules(tokens, rules, out.each[token].children, index); } /** * Last item in the list of tokens. we must * patch the rules here. */ if (isLast) { setLiteral(out, token, rules); return; } /** * Current item as an array */ if (isArray) { const item = setArray(out, token, isIndexedArray ? tokens[index] : '*'); return parseFieldForRules(tokens, rules, item, index); } /** * Falling back to object */ const item = setObject(out, token); return parseFieldForRules(tokens, rules, item.children, index); } /** * Parses the schema object to a tree of parsed schema. The * output is optimized for executing validation rules. * * @example * ``` * parser({ * 'users.*.username': 'required' * }) * * // output * * { * users: { * type: 'array', * rules: [], * each: { * '*': { * rules: [], * children: { * username: { * type: 'literal', * rules: [{ * name: 'required', * args: [] * }] * } * } * } * } * } * } * ``` */ function rulesParser(schema) { return Object .keys(schema) .reduce((result, field) => { const rules = schema[field]; let parsedRules = []; if (!rules) { throw new Error(`make sure to define rules for ${field}`); } if (typeof (rules) === 'string') { parsedRules = new haye_pipe_1.default(rules, new haye_array_presenter_1.default()).map((rule) => { return { name: toCamelCase(rule.name), args: rule.args }; }); } else { parsedRules = rules; } parseFieldForRules(field.split('.'), parsedRules, result); return result; }, {}); } exports.rulesParser = rulesParser; /** * Parses an object of messages to [[ParsedMessages]] list. The messages list * is simpler than rules tree, since compiler can use the schema tree to find * the appropriate messages from the flat list of messages. * * @example * ``` * parser({ * 'users.*.username.required': 'Username is required' * }) * * // output * * { * fields: { * 'users.*.username': { * required: 'Username is required' * } * }, * rules: {}, * } */ function messagesParser(schema) { return Object .keys(schema) .reduce((result, field) => { const message = schema[field]; const tokens = field.split('.'); const rule = toCamelCase(tokens.pop()); /** * If token length is 1, then it is a plain rule vs `field.rule` */ if (!tokens.length) { result.rules[rule] = message; return result; } const qualifiedName = tokens.join('.'); result.fields[qualifiedName] = result.fields[qualifiedName] || {}; result.fields[qualifiedName][rule] = message; return result; }, { fields: {}, rules: {} }); } exports.messagesParser = messagesParser;