Skip to content

Commit

Permalink
added: enforce.loose for loose shape enforcement style #492 (#505)
Browse files Browse the repository at this point in the history
  • Loading branch information
kapalex committed Nov 16, 2020
1 parent 3083ba4 commit 4c81bc0
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 9 deletions.
92 changes: 88 additions & 4 deletions packages/n4s/src/enforce/compounds/__tests__/shape.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import faker from 'faker';

import enforce from 'enforce';
import shape from 'shape';
import { shape, loose } from 'shape';

describe('Shape validation', () => {
describe('Base behavior', () => {
Expand Down Expand Up @@ -163,6 +163,25 @@ describe('Shape validation', () => {
});
});

describe('When field is in data but not in shape with loose option', () => {
it('Should succeed', () => {
expect(
shape(
{ user: 'example', password: 'x123' },
{ user: enforce.isString(), password: enforce.endsWith('23') },
{ loose: true }
)
).toBe(true);
expect(
shape(
{ user: 'example', password: 'x123' },
{ user: enforce.isString() },
{ loose: true }
)
).toBe(true);
});
});

describe('When field is in shape but not in data', () => {
it('Should fail', () => {
expect(
Expand All @@ -172,8 +191,42 @@ describe('Shape validation', () => {
)
).toBe(false);
});
it('Should fail even with loose', () => {
expect(
shape(
{ user: 'example' },
{ user: enforce.isString(), password: enforce.startsWith('x') },
{ loose: true }
)
).toBe(false);
});
});

describe('Behavior of loose compared to shape', () => {
it('Should succeed', () => {
expect(
loose(
{ user: 'example', password: 'x123' },
{ user: enforce.isString(), password: enforce.endsWith('23') }
)
).toBe(true);
expect(
loose(
{ user: 'example', password: 'x123' },
{ user: enforce.isString() }
)
).toBe(true);
});
it('Should fail even with loose', () => {
expect(
loose(
{ user: 'example' },
{ user: enforce.isString(), password: enforce.startsWith('x') }
)
).toBe(false);
});
})

describe('Handling of optional fields', () => {
it('Should allow optional fields to not be defined', () => {
expect(
Expand Down Expand Up @@ -294,11 +347,42 @@ describe('Shape validation', () => {
},
}).shape(shapeRules())
).toThrow();

expect(() =>
enforce({
user: {
age: faker.random.number(10),
friends: [1, 2, 3, 4, 5],
id: faker.random.uuid(),
name: {
first: faker.name.firstName(),
last: faker.name.lastName(),
},
shoeSize: 3,
username: faker.internet.userName(),
},
}).shape(shapeRules())
).toThrow();


enforce({
user: {
age: faker.random.number(10),
friends: [1, 2, 3, 4, 5],
id: faker.random.uuid(),
name: {
first: faker.name.firstName(),
last: faker.name.lastName(),
},
shoeSize: 3,
username: faker.internet.userName(),
},
}).loose(shapeRules({ loose: true }));
});
});
});

const shapeRules = () => ({
const shapeRules = (options) => ({
user: enforce.shape({
age: enforce.isNumber().isBetween(0, 10),
friends: enforce.optional(enforce.isArray()),
Expand All @@ -307,7 +391,7 @@ const shapeRules = () => ({
first: enforce.isString(),
last: enforce.isString(),
middle: enforce.optional(enforce.isString()),
}),
}, options),
username: enforce.isString(),
}),
}, options),
});
3 changes: 2 additions & 1 deletion packages/n4s/src/enforce/compounds/compounds.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import isArrayOf from 'isArrayOf';
import optional from 'optional';
import shape from 'shape';
import { shape, loose } from 'shape';

export default {
isArrayOf,
loose,
optional,
shape,
};
14 changes: 10 additions & 4 deletions packages/n4s/src/enforce/compounds/shape.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import runLazyRules from 'runLazyRules';
/**
* @param {Object} obj Data object that gets validated
* @param {Object} shapeObj Shape definition
* @param {Object} options
* @param {boolean} options.loose Ignore extra keys not defined in shapeObj
*/
export default function shape(obj, shapeObj) {
export function shape(obj, shapeObj, options) {
for (const key in shapeObj) {
const current = shapeObj[key];
const value = obj[key];
Expand All @@ -23,11 +25,15 @@ export default function shape(obj, shapeObj) {
}
}

for (const key in obj) {
if (!shapeObj[key]) {
return false;
if (!(options || {}).loose) {
for (const key in obj) {
if (!shapeObj[key]) {
return false;
}
}
}

return true;
}

export const loose = (obj, shapeObj) => shape(obj, shapeObj, { loose: true });
10 changes: 10 additions & 0 deletions packages/vest/src/typings/vest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,13 @@ export interface IEnforceRules<T = {}> {
lengthNotEquals: RuleNumeral<T>;
isNegative: RuleNumeral<T>;
isPositive: RuleNumeral<T>;
loose: <T>(shape: {
[key: string]: TEnforceLazy | TEnforceLazy[];
}) => RuleReturn<T>;
shape: <T>(shape: {
[key: string]: TEnforceLazy | TEnforceLazy[];
}, options?: {
loose?: boolean
}) => RuleReturn<T>;
}

Expand Down Expand Up @@ -254,8 +259,13 @@ type TEnforceLazy = {
isPositive: LazyEnforceWithNoArgs;
isBoolean: LazyEnforceWithNoArgs;
isNotBoolean: LazyEnforceWithNoArgs;
loose: <T>(shape: {
[key: string]: TEnforceLazy | TEnforceLazy[];
}) => TEnforceLazy;
shape: <T>(shape: {
[key: string]: TEnforceLazy | TEnforceLazy[];
}, options?: {
loose?: boolean
}) => TEnforceLazy;
optional: <T>(...rules: TEnforceLazy[]) => TEnforceLazy;
isArrayOf: <T>(...rules: TEnforceLazy[]) => TEnforceLazy;
Expand Down

0 comments on commit 4c81bc0

Please sign in to comment.