Skip to content

Commit

Permalink
add(n4s): enforce().message() for inline assertion text (#929)
Browse files Browse the repository at this point in the history
  • Loading branch information
shani-arnon committed Aug 15, 2022
1 parent 902b1bc commit 04f93f2
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 17 deletions.
6 changes: 6 additions & 0 deletions packages/n4s/src/__tests__/enforce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,11 @@ let enforce;
});
});
});

describe('Test enforce().message', () => {
it('Is enforce().message a function?', () => {
expect(enforce('').message).toBeInstanceOf(Function);
});
});
});
});
23 changes: 23 additions & 0 deletions packages/n4s/src/runtime/__tests__/message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,29 @@ describe('enforce..message()', () => {
});
});

describe('enforce().message()', () => {
it('should return message as a function', () => {
expect(enforce(3).message).toBeInstanceOf(Function);
});
it('should return message after chainning', () => {
expect(enforce(1).equals(1).message).toBeInstanceOf(Function);
});
it('should throw the message error on failure', () => {
expect(() => {
enforce('').message('octopus').equals('evyatar');
}).toThrow('octopus');
});
it('should throw the message error on failure with the last message that failed', () => {
expect(() => {
enforce(10)
.message('must be a number!')
.isNumeric()
.message('too high')
.lessThan(8);
}).toThrow('too high');
});
});

enforce.extend({
ruleWithFailureMessage: () => ({
pass: false,
Expand Down
55 changes: 38 additions & 17 deletions packages/n4s/src/runtime/enforceEager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ import { getRule, RuleValue, Args, RuleBase, KBaseRules } from 'runtimeRules';
import { transformResult } from 'transformResult';

type IRules = n4s.IRules<Record<string, any>>;
type TModifiers = {
message: (input: string) => EnforceEagerReturn;
};

export default function enforceEager(value: RuleValue): IRules {
const target = {} as IRules;
type EnforceEagerReturn = IRules & TModifiers;

export default function enforceEager(value: RuleValue): EnforceEagerReturn {
const target = {
message,
} as EnforceEagerReturn;
let customMessage: string | undefined = undefined;

// 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.
Expand All @@ -25,15 +33,16 @@ export default function enforceEager(value: RuleValue): IRules {
}

// We create a proxy intercepting access to the target object (which is empty).
const proxy: IRules = new Proxy(target, {
get: (_, ruleName: string) => {
const proxy: EnforceEagerReturn = new Proxy(target, {
get: (_, key: string) => {
// On property access, we identify if it is a rule or not.
const rule = getRule(ruleName);
const rule = getRule(key);

// If it is a rule, we wrap it with `genRuleCall` that adds the base enforce behavior
if (rule) {
return genRuleCall(proxy, rule, ruleName);
return genRuleCall(proxy, rule, key);
}
return target[key];
},
});

Expand All @@ -42,28 +51,40 @@ export default function enforceEager(value: RuleValue): IRules {
// 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) {
function genRuleCall(
target: EnforceEagerReturn,
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)
);
const transformedResult = ctx.run({ value }, () => {
return transformResult(rule(value, ...args), ruleName, value, ...args);
});

function enforceMessage() {
if (!isNullish(customMessage)) return customMessage;
if (isNullish(transformedResult.message)) {
return `enforce/${ruleName} failed with ${JSON.stringify(value)}`;
}
return StringObject(transformedResult.message);
}

// 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)
);
invariant(transformedResult.pass, enforceMessage());

return target;
};
}

function message(input: string): EnforceEagerReturn {
customMessage = input;
return proxy;
}
}

export type EnforceEager = (value: RuleValue) => IRules;
export type EnforceEager = (value: RuleValue) => EnforceEagerReturn;

1 comment on commit 04f93f2

@vercel
Copy link

@vercel vercel bot commented on 04f93f2 Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

vest-next – ./website

vest-next.vercel.app
vest-website.vercel.app
vest-next-ealush.vercel.app
vest-next-git-latest-ealush.vercel.app

Please sign in to comment.