From a42c77aba53bb012fb89557d446156bdd6c83e59 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 3 Mar 2022 16:24:58 -0500 Subject: [PATCH] feat: ability to specific or operations --- lib/__tests__/comparison.test.js | 22 ++++++++++++++++-- lib/index.js | 40 +++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/lib/__tests__/comparison.test.js b/lib/__tests__/comparison.test.js index 17d0da8..ef866ca 100644 --- a/lib/__tests__/comparison.test.js +++ b/lib/__tests__/comparison.test.js @@ -66,8 +66,26 @@ describe('Comparison', () => { describe('query', () => { it('should return truthy on dynamic matching', () => expect( - new Comparison(['dynamic={{age}}']).query(stub), - ).toMatchObject({})); + new Comparison({ + operand: '$or', + expressions: [ + 'dynamic={{age}}', + 'foo={{deeply.deeply.nested}}', + ], + }).query({ + ...stub, + deeply: { deeply: { nested: 'bar' } }, + }), + ).toMatchObject({ + $or: [ + { + dynamic: 21, + }, + { + foo: /bar/i, + }, + ], + })); it('should return as mongo query', () => expect(new Comparison(exp).query(stub)).toMatchObject({ diff --git a/lib/index.js b/lib/index.js index 742cc49..3776652 100644 --- a/lib/index.js +++ b/lib/index.js @@ -48,8 +48,19 @@ const getAssignment = { }), }; -const getValue = (target = {}, key) => - get(flat(target, { safe: true }), key); +const isObject = (v) => typeof v === 'object'; +const toPlainObject = (xs) => { + try { + return isObject(xs) ? JSON.parse(JSON.stringify(xs)) : xs; + } catch (e) { + return xs; + } +}; + +const flatten = (xs) => + flat(toPlainObject(xs), { + safe: true, + }); const keys = Object.keys(ops); const re = new RegExp(`[${keys.map((v) => `(${v})`).join('')}]`, 'i'); @@ -73,7 +84,7 @@ const getOp = (v) => { try { const prop = v.split('{{')[1].split('}}')[0]; return getOp( - v.replace(TEMPLATE_VARIABLE_PATTERN, getValue(obj, prop)), + v.replace(TEMPLATE_VARIABLE_PATTERN, get(obj, prop)), ); } catch (e) { throw new Error('Template variable not properly formatted'); @@ -98,7 +109,15 @@ const getOp = (v) => { class Comparison { constructor(exp, locale = 'en') { - this.expressions = hasLength(exp) ? exp : []; + if (Array.isArray(exp) || !isObject(exp)) { + this.expressions = hasLength(exp) ? exp : []; + this.operand = '$and'; + } else { + const { operand = '$and', expressions = [] } = exp; + this.expressions = expressions; + this.operand = operand; + } + this.locale = locale; } @@ -114,8 +133,10 @@ class Comparison { } query(obj = {}) { + const preflattened = flatten(obj); + return { - $and: this.eligible.reduce((a, params) => { + [this.operand]: this.eligible.reduce((a, params) => { let key; let value; let fn; @@ -123,7 +144,7 @@ class Comparison { if (Array.isArray(params)) { [key, value, fn] = params; } else if (isFn(params)) { - [key, value, fn] = params(obj); + [key, value, fn] = params(preflattened); } else { return a; } @@ -143,13 +164,16 @@ class Comparison { } eval(obj) { - return this.eligible.every((params) => { + const method = this.operand === '$and' ? 'every' : 'some'; + const preflattened = flatten(obj); + + return this.eligible[method]((params) => { const [key, value, validator] = isFn(params) ? params(obj) : params; return isFn(validator) - ? validator(getValue(obj, key), value, this.locale) + ? validator(get(preflattened, key), value, this.locale) : false; }); }