Skip to content

Commit

Permalink
feat: dynamic template variables
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeIbberson committed Jul 25, 2020
1 parent 0d33959 commit 5ed3861
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 618 deletions.
6 changes: 6 additions & 0 deletions lib/__tests__/comparison.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const stub = {
colleague: {
age: true,
},
dynamic: 21,
};

describe('Comparison', () => {
Expand All @@ -40,6 +41,11 @@ describe('Comparison', () => {
});

describe('eval', () => {
it('should return truthy on dynamic matching', () =>
expect(
new Comparison(['dynamic={{age}}']).eval(stub),
).toBeTruthy());

it('should return truthy', () =>
expect(new Comparison(exp).eval(stub)).toBeTruthy());

Expand Down
7 changes: 7 additions & 0 deletions lib/__tests__/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,11 @@ describe('Utils', () => {
it('should return falsy on false', () =>
expect(Utils.isEmpty(false)).toBeFalsy());
});

describe('Validate', () => {
it('should detect ObjectId as string', () =>
expect(
Utils.validate('5f0dc7481681ee00225164a7').numeric(),
).toBeFalsy());
});
});
41 changes: 33 additions & 8 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,31 @@ const getAssignment = {
const keys = Object.keys(ops);
const re = new RegExp(`[${keys.map((v) => `(${v})`).join('')}]`, 'i');

const hasLength = (v) => Array.isArray(v) && v.length;
const hasNegation = (v) => typeof v === 'string' && v.startsWith('!');
const TEMPLATE_VARIABLE_PATTERN = /{{\w*}}/gi;

const getOp = (v) =>
hasNegation(v)
const isString = (v) => typeof v === 'string';

const isFn = (v) => typeof v === 'function';
const hasLength = (v) => (Array.isArray(v) && v.length) || isFn(v);

const hasNegation = (v) => isString(v) && v.startsWith('!');
const hasTemplateVariables = (v) =>
isString(v) && new RegExp(TEMPLATE_VARIABLE_PATTERN).test(v);

const getOp = (v) => {
// returning an object will mean that we can assemble the op response
// based on the input eval data
if (hasTemplateVariables(v))
return (obj) => {
try {
const prop = v.split('{{')[1].split('}}')[0];
return getOp(v.replace(TEMPLATE_VARIABLE_PATTERN, obj[prop]));
} catch (e) {
throw new Error('Template variable not properly formatted');
}
};

return hasNegation(v)
? // reference the cast function conditionals
// this leverages how wildcards work, just the inverse
[v.replace('!', ''), '!', isEmpty]
Expand All @@ -65,6 +85,7 @@ const getOp = (v) =>
: a,
[],
);
};

class Comparison {
constructor(exp, locale = 'en') {
Expand Down Expand Up @@ -97,11 +118,15 @@ class Comparison {
}

eval(obj) {
return this.eligible.every(([key, value, validator]) =>
typeof validator === 'function'
return this.eligible.every((params) => {
const [key, value, validator] = isFn(params)
? params(obj)
: params;

return isFn(validator)
? validator(flat(obj)[key], value, this.locale)
: false,
);
: false;
});
}
}

Expand Down
10 changes: 9 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ const toMongoBoolean = (exists) => (exists ? true : { $ne: true });
const validate = (v) => ({
alpha: () => new RegExp(/^[A-Z]+$/, 'gi').test(v),
date: () => moment(v, moment.ISO_8601, true).isValid(),
numeric: () => !Number.isNaN(parseFloat(v)),
numeric: () => {
const float = parseFloat(v);

return (
// ensure that the parsed value doesn't drop any essential characters
// mongo's ObjectId, specifically, had this trouble
!Number.isNaN(float) && String(float).length === String(v).length
);
},
boolean: () => toBoolean(v) || v === 'false',
});

Expand Down
Loading

0 comments on commit 5ed3861

Please sign in to comment.