Skip to main content
Version: 4.x

Composing enforce rules

When you have rules that you often use together or different groups of rules that describe the same behavior, you can compose them into a single rule for easier reuse.

compose allows us to create an "AND" relationship wrapper around multiple rules which acts like the regular enforce function.

A simple use-case example: Let's say we have multiple entities in our app that share some common characteristics, but some that are unique. We can compose the different validation rules for the common characteristics into a single rule that we can reuse across multiple entities.

Let's assume the following:

  • Some of the entities in our app have an id property.
  • The person entity has a name property that includes a first, middle and last name.
  • The user entity has both an id and a name property. It also has a username property.
  • Users can also have a friends property, which is an array of other users.

Expressing this with basic enforce rules is easy, but can be cumbersome, and also not very reusable.

import { enforce } from 'vest';
import 'vest/enforce/schema'; // for the schema rules

enforce(userObj).shape({
id: enforce.number(),
name: enforce.shape({
first: enforce.string(),
middle: enforce.optional(enforce.string()),
last: enforce.string(),
}),
username: enforce.string(),
friends: enforce.optional(
enforce.arrayOf(
enforce.shape({
id: enforce.number(),
username: enforce.string(),
name: enforce.shape({
first: enforce.string(),
middle: enforce.optional(enforce.string()),
last: enforce.string(),
}),
}),
),
),
});

Instead, we can compose these different characteristics into composites that can later on be further reused.

import compose from 'vest/enforce/compose';
import 'vest/enforce/schema'; // for the schema rules

const Entity = compose(
enforce.loose({
id: enforce.number(),
}),
);

const Person = compose(
enforce.loose({
name: enforce.shape({
first: enforce.string(),
middle: enforce.optional(enforce.string()),
last: enforce.string(),
}),
}),
);

const User = compose(
Entity,
Person,
enforce.loose({
username: enforce.string(),
friends: enforce.optional(enforce.arrayOf(User)),
}),
);

This way, each composite can be used individually, but can also be composed together to create a more complex rule that can be easily reused.

Using these composites is as easy as either calling the from within other compound rules, or calling them directly within a Vest test just like the regular enforce function:

User(userObj); // Throws an error when failing

Some notes

When composing rules, be mindful when you are composing rules that have a shape rule inside of them. If these shape extend one another, you should probably use loose so they allow for extended properties.