-
-
Notifications
You must be signed in to change notification settings - Fork 85
/
enforceEager.ts
70 lines (58 loc) · 2.74 KB
/
enforceEager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { invariant, StringObject, isNullish } from 'vest-utils';
import eachEnforceRule from 'eachEnforceRule';
import { ctx } from 'enforceContext';
import isProxySupported from 'isProxySupported';
import { getRule, RuleValue, Args, RuleBase, KBaseRules } from 'runtimeRules';
import { transformResult } from 'transformResult';
type IRules = n4s.IRules<Record<string, any>>;
// eslint-disable-next-line max-lines-per-function
export default function enforceEager(value: RuleValue): IRules {
const target = {} as IRules;
// This condition is for when we don't have proxy support (ES5).
// In this case, we need to manually assign the rules to the target object on runtime.
// The follow up proxy block is used in case we do have proxy support, and we can assign each rule upon invocation.
if (!isProxySupported()) {
// We iterate over each of the rules, and add them to the target object being return by enforce
eachEnforceRule((ruleName: KBaseRules, ruleFn) => {
// We then wrap the rule with `genRuleCall` that adds the base enforce behavior
target[ruleName] = genRuleCall(target, ruleFn, ruleName);
});
return target;
}
// We create a proxy intercepting access to the target object (which is empty).
const proxy: IRules = new Proxy(target, {
get: (_, ruleName: string) => {
// On property access, we identify if it is a rule or not.
const rule = getRule(ruleName);
// If it is a rule, we wrap it with `genRuleCall` that adds the base enforce behavior
if (rule) {
return genRuleCall(proxy, rule, ruleName);
}
},
});
return proxy;
// This function is used to wrap a rule with the base enforce behavior
// It takes the target object, the rule function, and the rule name
// It then returns the rule, in a manner that can be used by enforce
function genRuleCall(target: IRules, rule: RuleBase, ruleName: string) {
return function ruleCall(...args: Args) {
// Order of operation:
// 1. Create a context with the value being enforced
// 2. Call the rule within the context, and pass over the arguments passed to it
// 3. Transform the result to the correct output format
const transformedResult = ctx.run({ value }, () =>
transformResult(rule(value, ...args), ruleName, value, ...args)
);
// On rule failure (the result is false), we either throw an error
// or throw a string value if the rule has a message defined in it.
invariant(
transformedResult.pass,
isNullish(transformedResult.message)
? `enforce/${ruleName} failed with ${JSON.stringify(value)}`
: StringObject(transformedResult.message)
);
return target;
};
}
}
export type EnforceEager = typeof enforceEager;