Skip to content

Commit

Permalink
feat(n4s): add shape and loose validations
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush committed Nov 10, 2021
1 parent 97175f1 commit 32fe8a5
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 22 deletions.
13 changes: 13 additions & 0 deletions packages/n4s/src/compounds/__tests__/allOf.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import enforce from 'enforce';

describe('allOf', () => {
describe('Lazy Assertions', () => {
describe('When all rules are satisfied', () => {
it('Should return a passing result', () => {
expect(
enforce.allOf(enforce.isArray(), enforce.longerThan(2)).run([1, 2, 3])
).toEqual({ pass: true });
});
});
});
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions packages/n4s/src/lib/runLazyRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { TRuleDetailedResult, TLazyRuleMethods } from 'ruleReturn';

export default function runLazyRule(
lazyRule: TLazyRuleMethods,
currentValue: any
): TRuleDetailedResult {
return lazyRule.run(currentValue);
}
11 changes: 0 additions & 11 deletions packages/n4s/src/lib/transformResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export function transformResult(
value: TRuleValue,
...args: TArgs
): TRuleDetailedResult {
const defaultResult = getDefaultResult(value);
validateResult(result);

// if result is boolean
Expand All @@ -24,7 +23,6 @@ export function transformResult(
};
} else {
return {
...defaultResult,
pass: result.pass,
...(result.message && {
message: optionalFunctionValue(
Expand All @@ -46,12 +44,3 @@ function validateResult(result: TRuleReturn): void {

throwError('Incorrect return value for rule: ' + JSON.stringify(result));
}

function getDefaultResult(value: TRuleValue): {
message: string;
pass?: boolean;
} {
return {
message: `invalid ${typeof value} value`,
};
}
11 changes: 6 additions & 5 deletions packages/n4s/src/runtime/genEnforceLazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,12 @@ export default function genEnforceLazy(key: string) {
}
}

export type TLazyRules = {
[P in keyof TCompounds]: (
...args: DropFirst<Parameters<TCompounds[P]>> | TArgs
) => TLazyRules & TLazyRuleMethods;
} &
export type TLazyRules = Record<string, (...args: TArgs) => TLazyRules> &
{
[P in keyof TCompounds]: (
...args: DropFirst<Parameters<TCompounds[P]>> | TArgs
) => TLazyRules & TLazyRuleMethods;
} &
{
[P in TBaseRules]: (
...args: DropFirst<Parameters<typeof baseRules[P]>> | TArgs
Expand Down
3 changes: 2 additions & 1 deletion packages/n4s/src/runtime/runtimeRules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import compounds from 'compounds';
import type { TRuleReturn } from 'ruleReturn';
import rules from 'rules';
import schema from 'schema';

export type TArgs = any[];

Expand All @@ -12,7 +13,7 @@ export type TRule = Record<string, TRuleBase>;

export type TBaseRules = keyof typeof baseRules;

const baseRules = Object.assign(rules(), compounds());
const baseRules = Object.assign(rules(), compounds(), schema());

function getRule(ruleName: string): TRuleBase {
return baseRules[ruleName as TBaseRules];
Expand Down
177 changes: 177 additions & 0 deletions packages/n4s/src/schema/__tests__/shape.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import enforce from 'enforce';

// The base behavior of 'loose' and 'shape' is practically the same
// so we cover them using the same tests.
describe.each(['loose', 'shape'])('enforce.%s', (methodName: string) => {
describe('lazy interface', () => {
it('Should return a passing return when tests are valid', () => {
expect(
enforce[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
}).run({ username: 'ealush', age: 31 })
).toEqual({ pass: true });
});

it('Should return a failing return when tests are invalid', () => {
expect(
enforce[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
}).run({ username: null, age: 0 })
).toEqual({ pass: false });
});

describe('nested shapes', () => {
it('Should return a passing return when tests are valid', () => {
expect(
enforce[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
address: enforce.shape({
street: enforce.isString(),
city: enforce.isString(),
state: enforce.isString(),
zip: enforce.isNumber(),
}),
}).run({
username: 'ealush',
age: 31,
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zip: 12345,
},
})
).toEqual({ pass: true });
});
it('Should return a failing return when tests are invalid', () => {
expect(
enforce[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
address: enforce.shape({
street: enforce.isString(),
city: enforce.isString(),
state: enforce.isString(),
zip: enforce.isNumber(),
}),
}).run({
username: 'ealush',
age: 31,
address: {
street: '123 Main St',
city: null,
},
})
).toEqual({ pass: false });
});
});
});

describe('eager interface', () => {
it('Should throw an error fora failing return', () => {
expect(() => {
enforce({ username: null, age: 0 })[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
});
}).toThrow();
});

it('Should return silently for a passing return', () => {
enforce({ username: 'ealush', age: 31 })[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
});
});

describe('nested shapes', () => {
it('Should return silently when tests are valid', () => {
enforce({
username: 'ealush',
age: 31,
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zip: 12345,
},
})[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
address: enforce.shape({
street: enforce.isString(),
city: enforce.isString(),
state: enforce.isString(),
zip: enforce.isNumber(),
}),
});
});
it('Should throw when tests are invalid', () => {
expect(() => {
enforce({
username: 'ealush',
age: 31,
address: {
street: '123 Main St',
city: null,
},
})[methodName]({
username: enforce.isString(),
age: enforce.isNumber().gt(18),
address: enforce.shape({
street: enforce.isString(),
city: enforce.isString(),
state: enforce.isString(),
zip: enforce.isNumber(),
}),
});
}).toThrow();
});
});
});
});

describe('enforce.shape excact matching', () => {
describe('lazy interface', () => {
it('Should return a failing return when value has non-enforced keys', () => {
expect(
enforce
.shape({ username: enforce.isString(), age: enforce.isNumber() })
.run({ username: 'ealush', age: 31, foo: 'bar' })
).toEqual({ pass: false });
});
});
describe('eager interface', () => {
it('Should throw an error when value has non-enforced keys', () => {
expect(() => {
enforce({ username: 'ealush', age: 31, foo: 'bar' }).shape({
username: enforce.isString(),
age: enforce.isNumber(),
});
}).toThrow();
});
});
});

describe('enforce.loose for loose matching', () => {
describe('lazy interface', () => {
it('Should return a passing return when value has non-enforced keys', () => {
expect(
enforce
.loose({ username: enforce.isString(), age: enforce.isNumber() })
.run({ username: 'ealush', age: 31, foo: 'bar' })
).toEqual({ pass: true });
});
});
describe('eager interface', () => {
it('Should return sliently return when value has non-enforced keys', () => {
enforce({ username: 'ealush', age: 31, foo: 'bar' }).loose({
username: enforce.isString(),
age: enforce.isNumber(),
});
});
});
});
21 changes: 21 additions & 0 deletions packages/n4s/src/schema/loose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { TRuleDetailedResult, TLazyRuleMethods } from 'ruleReturn';
import * as ruleReturn from 'ruleReturn';
import runLazyRule from 'runLazyRule';

export default function loose(
inputObject: Record<string, any>,
shapeObject: Record<string, TLazyRuleMethods>
): TRuleDetailedResult {
for (const key in shapeObject) {
const currentValue = inputObject[key];
const currentRule = shapeObject[key];

const res = runLazyRule(currentRule, currentValue);

if (!res.pass) {
return res;
}
}

return ruleReturn.passing();
}
8 changes: 8 additions & 0 deletions packages/n4s/src/schema/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import loose from 'loose';
import shape from 'shape';

export default function schema() {
return { shape, loose };
}

export type TSchema = ReturnType<typeof schema>;
22 changes: 22 additions & 0 deletions packages/n4s/src/schema/shape.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import hasOwnProperty from 'hasOwnProperty';

import loose from 'loose';
import type { TRuleDetailedResult, TLazyRuleMethods } from 'ruleReturn';
import * as ruleReturn from 'ruleReturn';

export default function shape(
inputObject: Record<string, any>,
shapeObject: Record<string, TLazyRuleMethods>
): TRuleDetailedResult {
const baseRes = loose(inputObject, shapeObject);
if (!baseRes.pass) {
return baseRes;
}
for (const key in inputObject) {
if (!hasOwnProperty(shapeObject, key)) {
return ruleReturn.failing();
}
}

return ruleReturn.passing();
}
14 changes: 9 additions & 5 deletions tsconfig.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 32fe8a5

Please sign in to comment.