Skip to content

Commit

Permalink
feat: recursion
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeIbberson committed Mar 4, 2022
1 parent b937b96 commit 052927f
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 25 deletions.
111 changes: 111 additions & 0 deletions lib/__tests__/comparison.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,66 @@ describe('Comparison', () => {
stub,
),
).toBeFalsy());

it('should run query recursively', () => {
expect(
new Comparison({
operand: '$or',
expressions: [
'age>22',
{
operand: '$or',
expressions: [
'language=us',
'color=blue',
// this the only matching condition
'firstName=Jon',
],
},
],
}).eval(stub),
).toBeTruthy();
});

it('should run query recursively', () => {
expect(
new Comparison({
operand: '$and',
expressions: [
'age>22',
{
operand: '$or',
expressions: [
'language=us',
'color=blue',
// this the only matching condition
'firstName=Jon',
],
},
],
}).eval(stub),
).toBeFalsy();
});

it('should run query recursively', () => {
expect(
new Comparison({
operand: '$and',
expressions: [
'age<22',
{
operand: '$or',
expressions: [
'language=us',
'color=blue',
// this the only matching condition
'firstName=Jon',
],
},
],
}).eval(stub),
).toBeTruthy();
});
});

describe('query', () => {
Expand Down Expand Up @@ -152,5 +212,56 @@ describe('Comparison', () => {

expect(result).toHaveLength(2);
});

it('should run query recursively', () => {
const query = new Comparison({
operand: '$and',
expressions: [
'foo=bar',
'quuz=thunk',
{
operand: '$or',
expressions: [
'age={{age}}',
['gender={{gender}}', 'role={{access.role}}'],
],
},
],
}).query({
age: 21,
gender: 'Male',
access: {
role: 'Tester',
},
});

expect(query).toEqual({
$and: [
{
foo: /bar/i,
},
{
quuz: /thunk/i,
},
{
$or: [
{
age: 21,
},
{
$and: [
{
gender: /Male/i,
},
{
role: /Tester/i,
},
],
},
],
},
],
});
});
});
});
88 changes: 63 additions & 25 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ const getAssignment = {
}),
};

const isFunction = (v) => typeof v === 'function';
const isObject = (v) => typeof v === 'object';

const toPlainObject = (xs) => {
try {
return isObject(xs) ? JSON.parse(JSON.stringify(xs)) : xs;
Expand Down Expand Up @@ -109,6 +111,10 @@ const getOp = (v) => {

class Comparison {
constructor(exp, locale = 'en') {
if (!Comparison.isAcceptableParam(exp)) {
throw new Error('Only accepts arrays and objects');
}

if (Array.isArray(exp) || !isObject(exp)) {
this.expressions = hasLength(exp) ? exp : [];
this.operand = '$and';
Expand All @@ -121,53 +127,76 @@ class Comparison {
this.locale = locale;
}

static isAcceptableParam(item) {
return (
(Array.isArray(item) && item.every((rule) => isString(rule))) ||
(isObject(item) && 'operand' in item)
);
}

static isValid(v) {
return hasNegation(v) || re.test(v);
}

static of(method, args, defaultLocale) {
return (item) => {
const out = new this(item, defaultLocale);
return isFunction(out[method]) ? out[method](args) : null;
};
}

get eligible() {
return this.expressions
.filter(Comparison.isValid)
.map(getOp)
.filter(hasLength);
}

recurse(method, args = {}) {
return this.expressions
.filter(Comparison.isAcceptableParam)
.map(Comparison.of(method, args, this.locale))
.filter(Boolean);
}

query(obj = {}) {
const preflattened = flatten(obj);

return {
[this.operand]: this.eligible.reduce((a, params) => {
let key;
let value;
let fn;

if (Array.isArray(params)) {
[key, value, fn] = params;
} else if (isFn(params)) {
[key, value, fn] = params(preflattened);
} else {
return a;
}

const v = isSerialized(value) ? value.split(',') : value;
let { name } = fn;

if (Array.isArray(v)) {
name = name === 'doesNotEqual' ? 'nin' : 'in';
}

return a.concat({
[key]: getAssignment[name](v),
});
}, []),
[this.operand]: this.eligible
.reduce((a, params) => {
let key;
let value;
let fn;

if (Array.isArray(params)) {
[key, value, fn] = params;
} else if (isFn(params)) {
[key, value, fn] = params(preflattened);
} else {
return a;
}

const v = isSerialized(value) ? value.split(',') : value;
let { name } = fn;

if (Array.isArray(v)) {
name = name === 'doesNotEqual' ? 'nin' : 'in';
}

return a.concat({
[key]: getAssignment[name](v),
});
}, [])
.concat(this.recurse('query', obj)),
};
}

eval(obj) {
const method = this.operand === '$and' ? 'every' : 'some';
const preflattened = flatten(obj);

return this.eligible[method]((params) => {
const output = this.eligible[method]((params) => {
const [key, value, validator] = isFn(params)
? params(obj)
: params;
Expand All @@ -176,6 +205,15 @@ class Comparison {
? validator(get(preflattened, key), value, this.locale)
: false;
});

try {
return this.recurse('eval', obj)
.concat(output)
.flat()
[method](Boolean);
} catch (e) {
return output;
}
}
}

Expand Down

0 comments on commit 052927f

Please sign in to comment.