folktale-validations2.12.1 • Public • Published
A library of validators using folktale's
Validation including utility functions for combining and composing validations, and constraint-based validation, allowing you to validate objects or object graphs. Includes easily customisable error message rendering, and is easily extended with your own validations.
Much of the basic validation relies on well tested predicates provided by ramda-adjunct.
The library is well tested (on Node 7, 8 and 9) and the validators you'll create yourself are easy to test.
The project is broken into:
- validators: Validators that work on a single value or a collection of values.
- helpers: Functions helping you create, combine or change the behaviour of validators.
- constraints: A system for validating whole objects or object graphs, with validation and transformation of values.
- failures: Rendering of failed validations to human-readable error messages.
yarn add folktale-validations
npm install folktale-validations
Many validators are included, but it is very easy to create your own. The following are all ready to use out of the box.
- validateIsNotNumber #### Complex Objects
- validateIsNotPlainObject #### Nil Values
- validateIsNotUndefined #### Emptyness
- validateIsNonEmptyString #### Validity
- validateIsNotValidDate #### Numericality
- validateIsNonNegative #### Truth
- validateArrayElements - Validate all elements with supplied validator
- validateIsArrayOf - Validate is array and validate all elements with supplied validator
- validateObjectValues - Validate values using a map of key-validator pairs
- validateRequiredKeys - Required keys must be present
- validateWhitelistedKeys - Keys must appear on whitelist
- validateExclusiveKeys - Only one key must appear from the supplied list
- validateIsWhitelistedValue - Value must appear on whitelist
- validateIsNotBlacklistedValue - Value must not appear on blacklist
- validateIsNumberWithUnit - Value must be a number followed by supplied unit
- validateObjectWithConstraints - Validate an object or object graph using a constraint object that describes a valid object
- allOfValidator - All validations must pass (will always run all validations)
- andValidator - Both validations must pass (will always run both validations)
- anyOfValidtor - Any validations must pass (will always run all validations)
- orValidator - Either validation must pass (will always run both validations)
- untilFailureValidator - All validations must pass (short-circuits on failure)
- predicateValidator - Create a validator using a simple predicate
- regExpValidator - Create a validator that uses a RegExp for validation
Note: All examples in this introduction can be run as working tests in:
src/__tests__/docs/readme.js and can be run using:
yarn run test:readme
npm run test:readme
Every validation you perform will return a Validation object which will be either a
Failure or a
Success. There are
isSuccess functions exported from
index.js to help you check, however you can also use
matchWith and a variety of other methods to handle both cases which are outlined in the link above.
- If it is a
Success, the object's
valuewill contain the validated value.
- If it is a
Failure, the object's
valuewill contain a payload describing the failure.
A payload is a simple object describing the failure and has three fields:
uid- the UID of the validator
value- the value that failed validation
args- an array of values relating to the failure, for example
validateIsWhiteListedValuewill include two arrays - one of all whitelisted values, and one of values discovered that weren't included in the whitelist.
This information can be rendered into a human-readable message using a Failure Renderer. The library ships with a preconfigured renderer which knows how to render failures from all included validators, including complex nested failures. You can supply your own messages for some or all of these validators, as outlined below. Outputting payload objects from the validators makes them easy to test.
Many of the validators are simple predicate validators - supply them with a value and they will either succeed or fail:
Example 1 - Predicate Validator
const validValue = `a`const successfulValidation =const invalidValue = 1const failedValidation =const message =
Other validators require configuring before use.
Example 2 - Association Validator
const configuredValidator =const validValue = `abc`const successfulValidation =const invalidValue = `a`const failedValidation =const message =
Validators can also validate Objects - either keys or values. In the following example, the values of an object are validated, using a different validator for each key.
Example 3 - Object Validator
const configuredValidator =const validValue =a: 1b: `example`c: 1 2 3const successfulValidation =const invalidValue =a: `example`b: truec:const failedValidation =const message =
An Array of values can also be validated, using a single validator for all the values in the array.
Example 4 - Array Validator
const configuredValidator =const validValue = /a/ /b/ /c/const successfulValidation =const invalidValue = /a/ `/b/` /c/const failedValidation =const message =
The library offers a number of helper functions for combining or composing validations. In the following example,
allOfValidator is used to compose two validations into a single validation:
Example 5 - Composed Validations
const configuredValidator =const validValue = `abcd`const successfulValidation =const invalidValue = 1const failedValidation =const message =
These validations can themselves be composed, for example:
Example 6 - Nested Composed Validations
const configuredValidator =const validValue = `abcd`const successfulValidation =const invalidValue = nullconst failedValidation =const message =
Using constraints allows you to describe what constitutes a valid object or nested object graph. It also allows you to tansform the received values and apply default values for missing props. This involves three steps:
- Create a constraint object
validateObjectWithConstraintswith a constraint object
- Validate an object
Note: As part of the validation process, the constraints object itself is validated, and you will recieve a Failed Validation explaining where the problem is if it is invalid.
Note: Take a look at
src/constraints/constraints.js to see the constraints object that is used to validate constraints objects supplied to
Example 7 - Constraints With Flat Object
There are a number of other valid attributes for the constraints object.
Validating the keys of the object
By default all object's are validated by two field validators - one that checks there are no keys present that aren't described by the constraitns, and one that checks that any required keys (see below) are present. You can use the
fieldsValidator attribute to supply an additional validator for the object's keys themselves. For example you might want to check that only one of a set of keys should appear per object.
If you want to allow arbitray keys on your object in addition to the keys you are validating you can use set the
whistelistKeys key on the constraint for that object. This will prevent any errors being thrown if a key is discovered thay is not defined in the constraints. This allows you to only validate a subset of an object's keys or use object maps that are entirely composed of arbitrary keys.
If a key must be present, you can add an
isRequired attribute to the constraints object for that field. This will cause validation to fail if the key does not appear on the object. Note: your constraints will be invalid if you use
defaultValue on the same field.
If you want to supply a defualt value for a key if it isn't present you can add a
defaultValue attribute. This add a key with the default value to the object when it is validated. Note: Nothing is mutated - the value returned will be a copy of the object with the key/value pair added.
If you want to transform a supplied value that has passed validation, you can supply a transformer function as a field's
transformer attribute. Note: the transformer will be applied after any
defaultValue. The transformer function should be unary and return the transformed value.
Example 8 - Using isRequired, defaultValue and transformer
const constraints =fields:name: `a`isRequired: truevalidator: validateIsStringtransformer: toUppername: `b`validator: validateIsBooleandefaultValue: trueconst confguredValidator =const validValue =a: `abc`const expectedValue =a: `ABC`b: trueconst successfulValidation =const invalidValue =b: falseconst failedValidation =const message =
Validating Object Graphs
You aren't limitted to flat objects. You can use full object graphs comprising of objects and arrays. To describe these graphs you use two additional attributes of the constraints object:
value. In the following example a complex object graph is validated. Note: your constraints will be invalid if you use
value on the same field.
Example 9 - Constraints With Object Graph
const constraints =fields:name: `a`isRequired: truevalidator: validateIsObjectvalue:fields:name: `a-a`isRequired: truevalidator: validateIsBooleanname: `a-b`isRequired: truevalidator: validateIsNonEmptyArraychildren:fields:name: `a-b-a`isRequired: truevalidator: validateIsStringconst confguredValidator =const validValue =a:`a-a`: true`a-b`:`a-b-a`: `abc``a-b-a`: `def`const successfulValidation =const invalidValue =a:`a-a`: true`a-b`:`a-b-a`: `abc``a-b-a`: 123const failedValidation =const message =})
Customising Existing Validators
Replacing Existing Messages
The messages ouput during rendering can be configured by passing a map of functions via a configuration object to
configureValidators(). Here you can override the validator messages by using one of the existing keys and you can add your own key/function pairs rendering failures from your own validators. The default validator messages can be found here:
Each key should map to a function that returns a formatted message for that validator. The
uid of the payload returned from a validator will be used to locate the appropriate function, which will then have the values in the payload's
args applied to it. It is recommended you use some kind of namespaced uid. This library uses uids like this:
Example 10 - Customising Validation Failure Messages
Creating Validator Based On Existing Validator
The simplest way to customise an existing valiadator is simply to configure it as we have done in previous examples. You can then export the configured validator for use throughout your application. However if you want to add your own message that is specific to the configured validator you can decorate the validator, supplying it with a new uid.
Example 11 - Creating Validator Based On Existing Validator
const newUID = `example.validateIsValidTitle`const newMessageFunction = `Wasn't a title: `const titles = `mr` `mrs` `miss` `ms` `dr` `mx`const failureRenderer: configuredFailureRenderer =const validateIsValidTitle =titlesconst failedValidation =const message =
Customising Constraint Validation
You can also customise the rendering of constraint-based validation failures. In this instance things are more complicated as there is significant formatting as well as text rendering. Again, you can pass in an object via the configuration object of
configureValdidators(). The default helpers can be found here:
Example 12 - Customising Constraint-based Validation Formatting
Adding Your Own Validators
Using existing helpers
There are a couple of helpers offered to use as a basis for your own validators -
regExpValidator. In the following example we'll create a validator that checks that a string doesn't have any whitespace.
Example 13 - Creating a Validator Using Helpers
const UID = `example.validateHasNoWhitespace`const newMessageFunction =const regExp = /^\S+$/const failureRenderer: configuredFailureRenderer =const validateHasNoWhitespace =const successfulValidation =const failedValidation =const message =
The validator interface is very simple. They should:
- Accept any configuration values first
- Accept the data to validate last
- Should be curried
- If the validation is succeeds they should return a
valueset to the validated data.
- If the validation fails they shoudl return a
valueset to a payload.
A payload is created using
toPayload() which takes three arguments:
- A UID for that validator
- The value that was validated
- (optional) an array of values to be supplied to the function that will render the message for this validator.
Example 14 - Creating a Validator From Scratch
const UID = `example.validateContainsChars`const newMessageFunction = `Didn't contain chars: `const failureRenderer: configuredFailureRenderer =const containsChars =charsconst Success Failure = Validationconst validateContainsChars =const configuredValidator =const successfulValidation =const failedValidation =const message =
Arguments Failure Renderer
If you want to output a message relating to arguments instead of an object, you can use the
argumentsFailureRenderer which will render a more appropriate message. It is also available from the object returned by
The NPM module includes:
libdirectory with imports transpiled for use by bundlers.
I welcome contributions, but ask you open an issue to discuss the bugfix or feature before you open a PR. Please keep any PRs as focussed as possible.
Tests are written with Jest.
Run tests in watch mode
Run tests once
yarn run test:noWatch
View HTML Coverage report
yarn run test:cov
yarn run build
Publish to NPM
yarn run publish:patch
yarn run publish:minor
yarn run publish:major
Simple predicates and their tests are generated from a series of consts and data files.
- Add the predicate name to
- Add predicate and negation to
- Add predicate and negation to
- Add predicate and negation export to
- Add predicate and negation data to
- Add default message to