Skip to content

Commit 9b2ae42

Browse files
shani-arnonealush
authored andcommitted
add(n4s): enforce().message() for inline assertion message (#923)
1 parent 14bfa60 commit 9b2ae42

File tree

3 files changed

+68
-17
lines changed

3 files changed

+68
-17
lines changed

packages/n4s/src/__tests__/enforce.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,11 @@ let enforce;
155155
});
156156
});
157157
});
158+
159+
describe('Test enforce().message', () => {
160+
it('Is enforce().message a function?', () => {
161+
expect(enforce('').message).toBeInstanceOf(Function);
162+
});
163+
});
158164
});
159165
});

packages/n4s/src/runtime/__tests__/message.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,29 @@ describe('enforce..message()', () => {
4949
});
5050
});
5151

52+
describe('enforce().message()', () => {
53+
it('should return message as a function', () => {
54+
expect(enforce(3).message).toBeInstanceOf(Function);
55+
});
56+
it('should return message after chainning', () => {
57+
expect(enforce(1).equals(1).message).toBeInstanceOf(Function);
58+
});
59+
it('should throw the message error on failure', () => {
60+
expect(() => {
61+
enforce('').message('octopus').equals('evyatar');
62+
}).toThrow('octopus');
63+
});
64+
it('should throw the message error on failure with the last message that failed', () => {
65+
expect(() => {
66+
enforce(10)
67+
.message('must be a number!')
68+
.isNumeric()
69+
.message('too high')
70+
.lessThan(8);
71+
}).toThrow('too high');
72+
});
73+
});
74+
5275
enforce.extend({
5376
ruleWithFailureMessage: () => ({
5477
pass: false,

packages/n4s/src/runtime/enforceEager.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@ import { getRule, RuleValue, Args, RuleBase, KBaseRules } from 'runtimeRules';
77
import { transformResult } from 'transformResult';
88

99
type IRules = n4s.IRules<Record<string, any>>;
10+
type TModifiers = {
11+
message: (input: string) => EnforceEagerReturn;
12+
};
1013

11-
export default function enforceEager(value: RuleValue): IRules {
12-
const target = {} as IRules;
14+
type EnforceEagerReturn = IRules & TModifiers;
15+
16+
export default function enforceEager(value: RuleValue): EnforceEagerReturn {
17+
const target = {
18+
message,
19+
} as EnforceEagerReturn;
20+
let customMessage: string | undefined = undefined;
1321

1422
// This condition is for when we don't have proxy support (ES5).
1523
// In this case, we need to manually assign the rules to the target object on runtime.
@@ -25,15 +33,16 @@ export default function enforceEager(value: RuleValue): IRules {
2533
}
2634

2735
// We create a proxy intercepting access to the target object (which is empty).
28-
const proxy: IRules = new Proxy(target, {
29-
get: (_, ruleName: string) => {
36+
const proxy: EnforceEagerReturn = new Proxy(target, {
37+
get: (_, key: string) => {
3038
// On property access, we identify if it is a rule or not.
31-
const rule = getRule(ruleName);
39+
const rule = getRule(key);
3240

3341
// If it is a rule, we wrap it with `genRuleCall` that adds the base enforce behavior
3442
if (rule) {
35-
return genRuleCall(proxy, rule, ruleName);
43+
return genRuleCall(proxy, rule, key);
3644
}
45+
return target[key];
3746
},
3847
});
3948

@@ -42,28 +51,41 @@ export default function enforceEager(value: RuleValue): IRules {
4251
// This function is used to wrap a rule with the base enforce behavior
4352
// It takes the target object, the rule function, and the rule name
4453
// It then returns the rule, in a manner that can be used by enforce
45-
function genRuleCall(target: IRules, rule: RuleBase, ruleName: string) {
54+
function genRuleCall(
55+
target: EnforceEagerReturn,
56+
rule: RuleBase,
57+
ruleName: string
58+
) {
4659
return function ruleCall(...args: Args) {
4760
// Order of operation:
4861
// 1. Create a context with the value being enforced
4962
// 2. Call the rule within the context, and pass over the arguments passed to it
5063
// 3. Transform the result to the correct output format
51-
const transformedResult = ctx.run({ value }, () =>
52-
transformResult(rule(value, ...args), ruleName, value, ...args)
53-
);
64+
const transformedResult = ctx.run({ value }, () => {
65+
return transformResult(rule(value, ...args), ruleName, value, ...args);
66+
});
67+
68+
function enforceMessage() {
69+
const shouldUseCustomMessage = !isNullish(customMessage);
70+
if (shouldUseCustomMessage) return customMessage;
71+
if (isNullish(transformedResult.message)) {
72+
return `enforce/${ruleName} failed with ${JSON.stringify(value)}`;
73+
}
74+
return StringObject(transformedResult.message);
75+
}
5476

5577
// On rule failure (the result is false), we either throw an error
5678
// or throw a string value if the rule has a message defined in it.
57-
invariant(
58-
transformedResult.pass,
59-
isNullish(transformedResult.message)
60-
? `enforce/${ruleName} failed with ${JSON.stringify(value)}`
61-
: StringObject(transformedResult.message)
62-
);
79+
invariant(transformedResult.pass, enforceMessage());
6380

6481
return target;
6582
};
6683
}
84+
85+
function message(input: string): EnforceEagerReturn {
86+
customMessage = input;
87+
return proxy;
88+
}
6789
}
6890

69-
export type EnforceEager = (value: RuleValue) => IRules;
91+
export type EnforceEager = (value: RuleValue) => EnforceEagerReturn;

0 commit comments

Comments
 (0)