UNPKG

@rimbu/deep

Version:

Tools to use handle plain JS objects as immutable objects

284 lines 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.match = void 0; var tslib_1 = require("tslib"); var base_1 = require("@rimbu/base"); /** * Returns true if the given `value` object matches the given `matcher`, false otherwise. * @typeparam T - the input value type * @typeparam C - utility type * @param source - the value to match (should be a plain object) * @param matcher - a matcher object or a function taking the matcher API and returning a match object * @param failureLog - (optional) a string array that can be passed to collect reasons why the match failed * @example * ```ts * const input = { a: 1, b: { c: true, d: 'a' } } * match(input, { a: 1 }) // => true * match(input, { a: 2 }) // => false * match(input, { a: (v) => v > 10 }) // => false * match(input, { b: { c: true }}) // => true * match(input, (['every', { a: (v) => v > 0 }, { b: { c: true } }]) // => true * match(input, { b: { c: (v, parent, root) => v && parent.d.length > 0 && root.a > 0 } }) * // => true * ``` */ function match(source, matcher, failureLog) { return matchEntry(source, source, source, matcher, failureLog); } exports.match = match; /** * Match a generic match entry against the given source. */ function matchEntry(source, parent, root, matcher, failureLog) { if (Object.is(source, matcher)) { // value and target are exactly the same, always will be true return true; } if (matcher === null || matcher === undefined) { // these matchers can only be direct matches, and previously it was determined that // they are not equal failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("value ".concat(JSON.stringify(source), " did not match matcher ").concat(matcher)); return false; } if (typeof source === 'function') { // function source values can only be directly matched var result = Object.is(source, matcher); if (!result) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("both value and matcher are functions, but they do not have the same reference"); } return result; } if (typeof matcher === 'function') { // resolve match function first var matcherResult = matcher(source, parent, root); if (typeof matcherResult === 'boolean') { // function resulted in a direct match result if (!matcherResult) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("function matcher returned false for value ".concat(JSON.stringify(source))); } return matcherResult; } // function resulted in a value that needs to be further matched return matchEntry(source, parent, root, matcherResult, failureLog); } if ((0, base_1.isPlainObj)(source)) { // source ia a plain object, can be partially matched return matchPlainObj(source, parent, root, matcher, failureLog); } if (Array.isArray(source)) { // source is an array return matchArr(source, parent, root, matcher, failureLog); } // already determined above that the source and matcher are not equal failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("value ".concat(JSON.stringify(source), " does not match given matcher ").concat(JSON.stringify(matcher))); return false; } /** * Match an array matcher against the given source. */ function matchArr(source, parent, root, matcher, failureLog) { if (Array.isArray(matcher)) { // directly compare array contents var length = source.length; if (length !== matcher.length) { // if lengths not equal, arrays are not equal failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("array lengths are not equal: value length ".concat(source.length, " !== matcher length ").concat(matcher.length)); return false; } // loop over arrays, matching every value var index = -1; while (++index < length) { if (!matchEntry(source[index], source, root, matcher[index], failureLog)) { // item did not match, return false failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("index ".concat(index, " does not match with value ").concat(JSON.stringify(source[index]), " and matcher ").concat(matcher[index])); return false; } } // all items are equal return true; } // matcher is plain object if (typeof matcher === 'object' && null !== matcher) { if ("every" in matcher) { return matchCompound(source, parent, root, tslib_1.__spreadArray(['every'], tslib_1.__read(matcher.every), false), failureLog); } if ("some" in matcher) { return matchCompound(source, parent, root, tslib_1.__spreadArray(['some'], tslib_1.__read(matcher.some), false), failureLog); } if ("none" in matcher) { return matchCompound(source, parent, root, tslib_1.__spreadArray(['none'], tslib_1.__read(matcher.none), false), failureLog); } if ("single" in matcher) { return matchCompound(source, parent, root, tslib_1.__spreadArray(['single'], tslib_1.__read(matcher.single), false), failureLog); } if ("someItem" in matcher) { return matchTraversal(source, root, 'someItem', matcher.someItem, failureLog); } if ("everyItem" in matcher) { return matchTraversal(source, root, 'everyItem', matcher.everyItem, failureLog); } if ("noneItem" in matcher) { return matchTraversal(source, root, 'noneItem', matcher.noneItem, failureLog); } if ("singleItem" in matcher) { return matchTraversal(source, root, 'singleItem', matcher.singleItem, failureLog); } } // matcher is plain object with index keys for (var index in matcher) { var matcherAtIndex = matcher[index]; if (!(index in source)) { // source does not have item at given index failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("index ".concat(index, " does not exist in source ").concat(JSON.stringify(source), " but should match matcher ").concat(JSON.stringify(matcherAtIndex))); return false; } // match the source item at the given index var result = matchEntry(source[index], source, root, matcherAtIndex, failureLog); if (!result) { // item did not match failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("index ".concat(index, " does not match with value ").concat(JSON.stringify(source[index]), " and matcher ").concat(JSON.stringify(matcherAtIndex))); return false; } } // all items match return true; } /** * Match an object matcher against the given source. */ function matchPlainObj(source, parent, root, matcher, failureLog) { if (Array.isArray(matcher)) { // the matcher is of compound type return matchCompound(source, parent, root, matcher, failureLog); } // partial object props matcher for (var key in matcher) { if (!(key in source)) { // the source does not have the given key failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("key ".concat(key, " is specified in matcher but not present in value ").concat(JSON.stringify(source))); return false; } // match the source value at the given key with the matcher at given key var result = matchEntry(source[key], source, root, matcher[key], failureLog); if (!result) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("key ".concat(key, " does not match in value ").concat(JSON.stringify(source[key]), " with matcher ").concat(JSON.stringify(matcher[key]))); return false; } } // all properties match return true; } /** * Match a compound matcher against the given source. */ function matchCompound(source, parent, root, compound, failureLog) { // first item indicates compound match type var matchType = compound[0]; var length = compound.length; // start at index 1 var index = 0; switch (matchType) { case 'every': { while (++index < length) { // if any item does not match, return false var result = matchEntry(source, parent, root, compound[index], failureLog); if (!result) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in compound \"every\": match at index ".concat(index, " failed")); return false; } } return true; } case 'none': { // if any item matches, return false while (++index < length) { var result = matchEntry(source, parent, root, compound[index], failureLog); if (result) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in compound \"none\": match at index ".concat(index, " succeeded")); return false; } } return true; } case 'single': { // if not exactly one item matches, return false var onePassed = false; while (++index < length) { var result = matchEntry(source, parent, root, compound[index], failureLog); if (result) { if (onePassed) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in compound \"single\": multiple matches succeeded"); return false; } onePassed = true; } } if (!onePassed) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in compound \"single\": no matches succeeded"); } return onePassed; } case 'some': { // if any item matches, return true while (++index < length) { var result = matchEntry(source, parent, root, compound[index], failureLog); if (result) { return true; } } failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in compound \"some\": no matches succeeded"); return false; } } } function matchTraversal(source, root, matchType, matcher, failureLog) { var index = -1; var length = source.length; switch (matchType) { case 'someItem': { while (++index < length) { if (matchEntry(source[index], source, root, matcher, failureLog)) { return true; } } failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in array traversal \"someItem\": no items matched given matcher"); return false; } case 'everyItem': { while (++index < length) { if (!matchEntry(source[index], source, root, matcher, failureLog)) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in array traversal \"everyItem\": at least one item did not match given matcher"); return false; } } return true; } case 'noneItem': { while (++index < length) { if (matchEntry(source[index], source, root, matcher, failureLog)) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in array traversal \"noneItem\": at least one item matched given matcher"); return false; } } return true; } case 'singleItem': { var singleMatched = false; while (++index < length) { if (matchEntry(source[index], source, root, matcher, failureLog)) { if (singleMatched) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in array traversal \"singleItem\": more than one item matched given matcher"); return false; } singleMatched = true; } } if (!singleMatched) { failureLog === null || failureLog === void 0 ? void 0 : failureLog.push("in array traversal \"singleItem\": no item matched given matcher"); return false; } return true; } } } //# sourceMappingURL=match.cjs.map