Skip to content

Commit

Permalink
patch: [n4s] inverse arg control for more correct lazy rule flow (#500)
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush authored Nov 13, 2020
1 parent bbe0159 commit 4059180
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 64 deletions.
3 changes: 3 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
"proxySupported": [
"./packages/n4s/src/lib/proxySupported.js"
],
"runLazyRules": [
"./packages/n4s/src/lib/runLazyRules.js"
],
"transformResult": [
"./packages/n4s/src/lib/transformResult.js"
],
Expand Down
48 changes: 22 additions & 26 deletions packages/n4s/src/enforce/__tests__/enforce.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,41 +91,37 @@ const suite = ({ withProxy, requirePath }) =>
});

it('Should retain all lazy functions in an array as a property of the returned object', () => {
expect(enforce.isEmpty()[RUN_RULE]).toBeInstanceOf(Array);
expect(enforce.isEmpty().isArray()[RUN_RULE]).toBeInstanceOf(Array);
expect(enforce.isEmpty()[RUN_RULE]).toBeInstanceOf(Function);
expect(enforce.isEmpty().isArray()[RUN_RULE]).toBeInstanceOf(Function);
});

it('Should store all the provided rules in the returned array', () => {
const res = enforce.isEmpty().isArray().equals()[RUN_RULE];
expect(res).toHaveLength(3);
expect(res[0].name).toBe('isEmpty');
expect(res[1].name).toBe('isArray');
expect(res[2].name).toBe('equals');
expect(typeof res[0]).toBe('function');
expect(typeof res[1]).toBe('function');
expect(typeof res[2]).toBe('function');
it('Should run all chained rules with the test function', () => {
const r1 = jest.fn(() => true);
const r2 = jest.fn(() => true);
const r3 = jest.fn(() => true);
enforce.extend({
r1,
r2,
r3,
});
enforce.r1().r2().r3()[RUN_RULE]('some_value');
expect(r1).toHaveBeenCalledWith('some_value');
expect(r2).toHaveBeenCalledWith('some_value');
expect(r3).toHaveBeenCalledWith('some_value');
});

it('Should produce correct result when run', () => {
expect(enforce.isEmpty()[RUN_RULE].every(fn => fn([]))).toBe(true);
expect(enforce.isEmpty()[RUN_RULE].every(fn => fn([1, 2, 3]))).toBe(
false
);
expect(enforce.isNumeric()[RUN_RULE].every(fn => fn('555'))).toBe(true);
expect(enforce.greaterThan(10)[RUN_RULE].every(fn => fn(20))).toBe(
true
);
expect(enforce.greaterThan(20)[RUN_RULE].every(fn => fn(10))).toBe(
false
);
expect(enforce.greaterThan(10)[RUN_RULE].every(fn => fn(4))).toBe(
false
);
expect(enforce.isEmpty()[RUN_RULE]([])).toBe(true);
expect(enforce.isEmpty()[RUN_RULE]([1, 2, 3])).toBe(false);
expect(enforce.isNumeric()[RUN_RULE]('555')).toBe(true);
expect(enforce.greaterThan(10)[RUN_RULE](20)).toBe(true);
expect(enforce.greaterThan(20)[RUN_RULE](10)).toBe(false);
expect(enforce.greaterThan(10)[RUN_RULE](4)).toBe(false);
const fn = jest.fn(() => true);
enforce.extend({
getArgs: fn,
});
enforce.getArgs(2, 3, 4, 5, 6, 7)[RUN_RULE].every(fn => fn(1));
enforce.getArgs(2, 3, 4, 5, 6, 7)[RUN_RULE](1);
// // One should be first
expect(fn).toHaveBeenCalledWith(1, 2, 3, 4, 5, 6, 7);
});
Expand Down
7 changes: 2 additions & 5 deletions packages/n4s/src/enforce/compounds/optional.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asArray from 'asArray';
import { RUN_RULE } from 'enforceKeywords';
import { isNull } from 'isNull';
import { isUndefined } from 'isUndefined';
import runLazyRules from 'runLazyRules';

/**
* @param {Array} ObjectEntry Object and key leading to current value
Expand All @@ -15,7 +14,5 @@ export default function optional([obj, key], ...ruleGroups) {
return true;
}

return asArray(ruleGroups).every(ruleGroup => {
return ruleGroup[RUN_RULE].every(fn => fn(obj[key]));
});
return runLazyRules(ruleGroups, obj[key]);
}
14 changes: 9 additions & 5 deletions packages/n4s/src/enforce/compounds/shape.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import asArray from 'asArray';
import { RUN_RULE } from 'enforceKeywords';
import optional from 'optional';
import runLazyRules from 'runLazyRules';
/**
* @param {Object} obj Data object that gets validated
* @param {*} shapeObj Shape definition
* @param {Object} shapeObj Shape definition
*/
export default function shape(obj, shapeObj) {
for (const key in shapeObj) {
const current = shapeObj[key];
const value = obj[key];

if (
!asArray(current[RUN_RULE]).every(fn => {
return fn(fn.name === optional.name ? [obj, key] : value);
!runLazyRules(current, ruleName => {
// Optional is a unique rule - it needs to know whether the
// value is an actual property of the provided object, so
// instead of passing just the value as we do in all other cases
// in the case of `optional` we pass the object and the key
// so it can check it.
return ruleName === optional.name ? [obj, key] : value;
})
) {
return false;
Expand Down
19 changes: 13 additions & 6 deletions packages/n4s/src/enforce/enforce.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import compounds from 'compounds';
import { RUN_RULE } from 'enforceKeywords';
import runner from 'enforceRunner';
import isFunction from 'isFunction';
import isRule from 'isRule';
import proxySupported from 'proxySupported';
import rules from 'rules';
Expand All @@ -10,8 +11,7 @@ const rulesObject = Object.assign(rules(), compounds);
let rulesList = proxySupported() ? null : Object.keys(rulesObject);

const Enforce = value => {
const target = proxySupported() ? enforce : {};
const proxy = genRuleProxy(target, ruleName => (...args) => {
const proxy = genRuleProxy(enforce, ruleName => (...args) => {
runner(rulesObject[ruleName], value, args);
return proxy;
});
Expand All @@ -38,11 +38,11 @@ function genRuleProxy(target, output) {
if (proxySupported()) {
return new Proxy(target, {
get: (target, fnName) => {
if (!isRule(rulesObject, fnName)) {
return target[fnName];
if (isRule(rulesObject, fnName)) {
return output(fnName);
}

return output(fnName);
return target[fnName];
},
});
} else {
Expand Down Expand Up @@ -77,7 +77,14 @@ function bindLazyRule(ruleName) {
const returnvalue = genRuleProxy({}, addFn);

return Object.assign(returnvalue, {
[RUN_RULE]: registeredRules,
[RUN_RULE]: getValue => {
return registeredRules.every(fn => {
// This inversion of control when getting the value is
// required in order to pass the function over to `shape`
// so it can make the decision which args to pass to `optional`
return fn(isFunction(getValue) ? getValue(fn.name) : getValue);
});
},
});
};

Expand Down
6 changes: 6 additions & 0 deletions packages/n4s/src/lib/runLazyRules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import asArray from 'asArray';
import { RUN_RULE } from 'enforceKeywords';

export default function runLazyRules(ruleGroups, value) {
return asArray(ruleGroups).every(ruleGroup => ruleGroup[RUN_RULE](value));
}
44 changes: 22 additions & 22 deletions packages/vest/src/typings/vest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ interface IEnforce {
}

type LazyNumeral = (expected: TNumeral) => TEnforceLazy;
type JustLazy = () => TEnforceLazy;
type LazyEnforceWithNoArgs = () => TEnforceLazy;
type LazyString = (str: string) => TEnforceLazy;
type LazyRange = (start: number, end: number) => TEnforceLazy;
type LazyMatches = (expected: string | RegExp) => TEnforceLazy;
Expand Down Expand Up @@ -228,32 +228,32 @@ type TEnforceLazy = {
numberNotEquals: LazyNumeral;
matches: LazyMatches;
notMatches: LazyMatches;
isUndefined: JustLazy;
isArray: JustLazy;
isEmpty: JustLazy;
isEven: JustLazy;
isNumber: JustLazy;
isNaN: JustLazy;
isNotNaN: JustLazy;
isNumeric: JustLazy;
isOdd: JustLazy;
isTruthy: JustLazy;
isFalsy: JustLazy;
isString: JustLazy;
isNotArray: JustLazy;
isNotEmpty: JustLazy;
isNotNumber: JustLazy;
isNotNumeric: JustLazy;
isUndefined: LazyEnforceWithNoArgs;
isArray: LazyEnforceWithNoArgs;
isEmpty: LazyEnforceWithNoArgs;
isEven: LazyEnforceWithNoArgs;
isNumber: LazyEnforceWithNoArgs;
isNaN: LazyEnforceWithNoArgs;
isNotNaN: LazyEnforceWithNoArgs;
isNumeric: LazyEnforceWithNoArgs;
isOdd: LazyEnforceWithNoArgs;
isTruthy: LazyEnforceWithNoArgs;
isFalsy: LazyEnforceWithNoArgs;
isString: LazyEnforceWithNoArgs;
isNotArray: LazyEnforceWithNoArgs;
isNotEmpty: LazyEnforceWithNoArgs;
isNotNumber: LazyEnforceWithNoArgs;
isNotNumeric: LazyEnforceWithNoArgs;
isNotBetween: LazyRange;
isNotString: JustLazy;
isNotString: LazyEnforceWithNoArgs;
inside: LazyInside;
notInside: LazyInside;
lengthEquals: LazyNumeral;
lengthNotEquals: LazyNumeral;
isNegative: JustLazy;
isPositive: JustLazy;
isBoolean: JustLazy;
isNotBoolean: JustLazy;
isNegative: LazyEnforceWithNoArgs;
isPositive: LazyEnforceWithNoArgs;
isBoolean: LazyEnforceWithNoArgs;
isNotBoolean: LazyEnforceWithNoArgs;
shape: <T>(shape: {
[key: string]: TEnforceLazy | TEnforceLazy[];
}) => TEnforceLazy;
Expand Down

0 comments on commit 4059180

Please sign in to comment.