This is a library of validation
transform
combinators. The main idea is to produce
validation errors in the same shape as the data structure being validated. This
way validation errors can be accessed at the same path as the data and can be
mechanically associated with the corresponding elements of the validated data
structure.

Note that the ▶
links take
you to a live version of this page and that there is a
playground
for sharing examples.

- Examples
- Reference
- Elimination
- Primitive
- Predicates
- Elaboration
- Logical
- Arrays
- Functions
- Objects
- Conditional
`V.cases(...[(value, index) => testable, rule][, [rule]]) ~> rule`

^{v0.3.0}`V.casesOf(traversal, ...[(value, index) => testable, rule][, [rule]]) ~> rule`

^{v0.3.4}`V.ifElse((value, index) => testable, rule, rule) ~> rule`

^{v0.3.0}

- Dependent
- Recursive
- Transformation
- Ad-hoc
- Promotion
`V.promote(...[rule[, (value, index) => value]]) ~> rule`

^{v0.3.6}`V.upgrades(...[(value, index) => testable, rule[, async (value, index) => value]]) ~> rule`

^{v0.3.6}`V.upgradesOf(traversal, ...[(value, index) => testable, rule[, async (value, index) => value]]) ~> rule`

^{v0.3.6}

- Tips
- Known caveats
- Related work

The following sections briefly describe some examples based on actual use cases
of this library.

Imagine a UI — or take a look at this live
example — for editing a data
structure that is an array (table) of objects (records) that have a `date`

field
and an `event`

field:

```
[
{"date": "2017-09-11", "event": "EFSA-H"},
{"date": "2017-09-20", "event": "EFSA-T"},
{"date": "", "event": "EFSA-T"}
]
```

We need to validate that each object has a valid date and an event and that
dates and events are unique. Furthermore, we wish to give feedback on all
elements with errors so as to guide the user.

Here is a sample set of rules

```
const rules = V.choose(events => V.arrayIx(V.props({
date: V.and(
[isNonEmpty, 'required'],
[isValidDate, 'yyyy-mm-dd'],
[isUniqueBy('date', events), 'duplicate']),
event: V.and(
[isNonEmpty, 'required'],
[isUniqueBy('event', events), 'duplicate'])
})))
```

where

```
const isNonEmpty = R.identity
function isUniqueBy(p, xs) {
const counts = L.counts([L.elems, p], xs)
return x => counts.get(x) <= 1
}
const isValidDate = R.test(/^\d{4}-\d{2}-\d{2}$/)
```

to give such validation feedback. The rules basically just follow the structure
of the data.

Validating with those rules we get a data structure with the potential error
feedback at the same location as the offending element:

```
V.errors(rules, [
{"date": "2017-09-11", "event": "EFSA-H"},
{"date": "2017-09-20", "event": "EFSA-T"},
{"date": "", "event": "EFSA-T"}
])
```

The result tells us that the first object is valid (i.e. there are no validation
errors in it). The `event`

in the second object is a duplicate. The third
object is missing a date and the event is a duplicate.

The interface file of this library,
partial.lenses.validation.js, uses the
library itself to specify the contracts for the exports.

Assuming `process.env.NODE_ENV`

is not `"production"`

and you pass invalid
arguments to a function of this library, you will likely get an error message.
For example,

```
V.validate(
V.casesOf(
'type',
[
R.identical('number'),
V.props({
type: R.is(String),
value: R.isNumber
})
],
[
R.identical('boolean'),
V.props({
type: R.is(String),
value: R.is(Boolean)
})
],
),
{
type: 'boolean',
value: false
}
)
```

throws an error, because `R.isNumber`

is not defined. The error is thrown as
soon as the call to `V.props`

is made.

Examples of other libraries using Partial Lenses Validation for contract
checking:

The combinators provided by this library
are available as named imports. Typically one just imports the library as:

```
import * as V from 'partial.lenses.validation'
```

This library is actually built on top of Partial
Lenses
transforms. It is also
typical to use e.g. Ramda, bound as `R`

in examples, to
implement predicates.

To use a validation rule one runs it using one of the elimination functions.

In case a validation rule is fully synchronous, it is better to use a
synchronous elimination function, because synchronous validation is faster than
asynchronous validation.

`V.accepts(rule, data)`

runs the given validation rule on the given data and
simply returns `true`

in case the data is accepted and `false`

if not.

For example:

```
V.accepts(V.arrayIx(R.is(String)), ['Yes', 'No'])
```

`V.errors(rule, data)`

runs the given validation rule on the given data. In
case the data is accepted by the rule, the result is `undefined`

. Otherwise the
result is an object structure in the shape of the data structure containing the
validation errors.

For example:

```
V.errors(
V.props({
no: R.is(Number),
yes: R.is(String)
}),
{
yes: 101,
}
)
```

Note that in case a validation error would be `undefined`

, a `null`

is reported
instead.

`V.validate(rule, data)`

runs the given validation rule on the given input data.
In case the data is accepted, the validated output data is returned. In case
the data is rejected, an `Error`

object
is thrown whose message is the stringified validation error and that has an
extra `errors`

property that has the (non-stringified) validation errors.

For example:

```
V.validate(
V.props({
missing: R.is(String)
}),
{
unexpected: 'field'
}
)
```

In case a validation rule contains asynchronous parts, it is necessary to use
one of the asynchronous elimination functions. Functions that are *allowed, but
not required*, to be asynchronous are indicated in the documentation using the
`async`

keyword.

The below `ghInfoOfAsync`

function is a simple asynchronous function that tries
to use the public GitHub search
API to search for
information on a GitHub project of specified name:

```
async function ghInfoOfAsync(name) {
const q = encodeURIComponent(name)
const res = await fetch(`https://api.github.com/search/repositories?q=${q}`)
const body = await res.json()
return L.get(['items', L.find(R.whereEq({name}))], body)
}
```

`V.acceptsAsync(rule, data)`

runs the given validation rule on the given data
like `V.accepts`

except that the validation rule is allowed to
contain asynchronous validation predicates and
transformations. The result will always be returned as a
promise.

`V.errorsAsync(rule, data)`

runs the given validation rule on the given data
like `V.errors`

except that the validation rule is allowed to
contain asynchronous validation predicates and
transformations. The result will always be returned as a
promise.

For example:

```
V.errorsAsync(
V.arrayId(
R.pipeP(ghInfoOfAsync, L.get('stargazers_count'), R.lte(100))
),
[
'partial.lenses',
'partial.lenses.validation'
]
).catch(R.identity).then(console.log)
```

`V.tryValidateAsyncNow(rule, data)`

runs the given validation rule on the given
data like `V.validateAsync`

except that in case the
validation result is synchronously available it is returned or thrown
immediately as is without wrapping it inside a
promise.
In case the result is not available synchronously, a promise is returned.

`V.tryValidateAsyncNow`

can be used for wrapping asynchronous functions, for
example, because the first stage of validating a function is always synchronous.

For example:

```
const ghInfoOfAsyncChecked = V.tryValidateAsyncNow(
V.dependentFn(
V.args(R.and(R.is(String), V.not(R.isEmpty))),
name => V.optional(
V.propsOr(V.accept, {
name: R.identical(name),
stargazers_count: R.is(Number)
})
)
),
ghInfoOfAsync
)
```

`V.validateAsync(rule, data)`

runs the given validation rule on the given data
like `V.validate`

except that the validation rule is allowed to
contain asynchronous validation predicates and
transformations. The result, whether accepted or rejected, is
returned as a
promise.

For example:

```
V.validateAsync(
V.arrayId(
V.and(
R.is(String),
V.acceptWith(ghInfoOfAsyncChecked),
V.keep(
'name',
V.propsOr(V.remove, {
name: R.is(String),
stargazers_count: V.and(
R.is(Number),
[R.lte(1000), n => `Only ${n} stars. You know how to fix it!`]
)
})
)
)
),
[
'partial.lenses',
'partial.lenses.validation'
]
).catch(R.identity).then(console.log)
```

It is also possible to run validation rules with an arbitrary computational
monad such as a monad based on observables.

`V.run({Monad, onAccept, onReject}, rule, data)`

runs the given validation rule
on the given data using the specified computational monad and either calls the
accept callback with the validated data or the reject callback with the
validation errors.

The parameters `Monad`

, `onAccept`

, and `onReject`

are optional and default to
what `V.validate`

uses. The `Monad`

parameter needs to be a
Static Land compatible
Monad with all the four functions.
If you specify the `Monad`

, you will likely want to specify both `onAccept`

and
`onReject`

as well.

At the most basic level a rule either accepts or rejects the value in focus.

`V.accept`

accepts the current focus as is. `V.accept`

should be rarely used as
it performs no validation whatsoever.

`V.acceptAs(value)`

accepts the current focus and replaces it with the given
value. `V.acceptAs`

is rarely used alone, because it performs no validation as
such, and is usually combined with e.g. `V.and`

.

For example:

```
V.validate(V.and(R.identical(1), V.acceptAs('one')), 1)
```

`V.acceptWith(fn)`

accepts the current focus and replaces it with the value
returned by the given possibly async function. `V.acceptWith`

is rarely used alone, because it performs no validation as such, and is usually
combined with e.g. `V.and`

.

In a logical `V.or`

each rule gets the same value as input and the
result of the first accepting rule becomes the result. In a logical
`V.and`

the output of a previous rule becomes the input of the next
rule.

For example:

```
V.validate(
V.and(
V.or(
V.and(
R.is(Number),
V.acceptWith(n => `number ${n}`)
),
R.is(String)
),
V.acceptWith(R.toUpper)
),
10
)
```

`V.reject`

rejects the current focus as is. In case the focus is `undefined`

,
the error will be `null`

instead.

The idea is that the validation error data structure simply contains the parts
of the validated data structure that weren't accepted. This usually allows a
programmer who is familiar with the system to quickly diagnose the problem.

For example:

```
V.errors(
V.propsOr(V.reject, {}),
{
thisField: 'is not allowed',
}
)
```

`V.rejectAs(error)`

rejects the current focus as the given error value. In case
the given error value is `undefined`

, it is replaced with `null`

instead.

Using `V.rejectAs`

one can specify what the error should be. This way an error
data structure can be constructed that can, for example, contain error messages
to be displayed in a form that an end user can understand.

For example:

```
V.errors(
V.propsOr(V.rejectAs('Unexpected field'), {}),
{
thisField: 'is not allowed',
}
)
```

`V.rejectWith(fn)`

rejects the current focus with the error value returned by
the given possibly async function from the value in focus. In
case the return value is `undefined`

, the error will be `null`

instead.

Using `V.rejectWith`

one can specify what the error should be depending on the
value in focus. This allows detailed error messages to be constructed.

For example:

```
V.errors(
V.propsOr(
V.rejectWith(value => `Unexpected field: ${JSON.stringify(value)}`),
{}
),
{
thisField: 'is not allowed',
}
)
```

`V.remove`

replaces the not yet rejected value in focus with `undefined`

,
which means that it is removed from the surrounding array or object. Beware
that `V.remove`

by itself performs no validation. You usually combine
`V.remove`

with e.g. `V.and`

or `V.propsOr`

.

For example:

```
V.validate(
V.propsOr(V.remove, {
required: R.is(String)
}),
{
required: 'field',
unexpected: 'and removed'
}
)
```

Unary (and binary) functions are implicitly treated as predicates and lifted to
validation rules using `V.where`

.

`V.where(predicate)`

, or using the shorthand notation `predicate`

, lifts the
given possibly async predicate to a validation rule. In case
the focus does not satisfy the predicate, it is rejected with
`V.reject`

.

Note that explicitly calling `V.where`

is typically unnecessary, because unary
(and binary) functions are implicitly treated as predicates and lifted with
`V.where`

to rules in this library.

For example:

```
V.validate(
V.props({
isNumber: V.where(R.is(Number)),
alsoNumber: R.is(Number)
}),
{
isNumber: 101,
alsoNumber: 42
}
)
```

In case the predicate throws an exception, the focus is rejected with the
exception as the error value.

It is also possible to modify the error after a rule has rejected the focus.

`V.modifyError(fn, rule)`

, or using the shorthand notation `[rule, fn]`

, acts
like `rule`

except that in case the rule rejects the focus, the error is
computed using the given possibly async function that is given
the value in focus, the error from the rule and the index of the focus. In case
the given function returns `undefined`

, the error will be `null`

instead.

Note that the shorthand notation `[rule, fn]`

can be used instead of a more
verbose function call. This shorthand is provided to make it more convenient to
attach detailed error messages to rules.

For example:

```
V.errors(
V.choose(data => {
const expectedSum = L.sum(['numbers', L.elems], data)
return V.props({
numbers: V.arrayIx(R.is(Number)),
sum: [
R.identical(expectedSum),
actualSum => `Expected ${expectedSum} instead of ${actualSum}`
]
})
}),
{
numbers: [3, 1, 4],
sum: 9
}
)
```

`V.setError(error, rule)`

, or using the shorthand notation `[rule, error]`

when
`error`

is not a function, acts like `rule`

except that in case the rule rejects
the focus, the given error is used instead. In case the given error is
`undefined`

, it is replaced with `null`

instead.

Note that the shorthand notation `[rule, error]`

can be used instead of a more
verbose function call when the `error`

is not a function. In case `error`

is a
function, it is called like with `V.modifyError`

. This
shorthand is provided to make it more convenient to attach detailed error
messages to rules.

For example:

```
V.errors(
V.choose(data => {
const expectedSum = L.sum(['numbers', L.elems], data)
return V.props({
numbers: V.arrayIx(R.is(Number)),
sum: [
R.identical(expectedSum),
`Expected ${expectedSum}`
]
})
}),
{
numbers: [3, 1, 4],
sum: 9
}
)
```

Logical connectives provide a simple means to combine rules to form more complex
rules.

`V.and(rule1, ..., ruleN)`

validates the value in focus with all of the given
rules one-by-one starting from the first given rule. In case some rule rejects
the focus, that becomes the result of `V.and`

. Otherwise the result of `V.and`

is the accepted result produced by passing the original focus through all of the
given rules. Note that `V.and`

is not curried like `V.both`

.

`V.both(rule1, rule2)`

validates the value in focus with both of the given rules
starting with the first of the given rules. `V.both(rule1, rule2)`

is
equivalent to `V.and(rule1, rule2)`

.

`V.either(rule1, rule2)`

validates the value in focus with either of the given
rules starting with the first of the given rules. `V.either(rule1, rule2)`

is
equivalent to `V.or(rule1, rule2)`

.

`V.not(rule)`

validates the value in focus with the given rule. In case the
rule accepts the focus, `V.not`

rejects it instead. In case the rule rejects
the focus, `V.not`

accepts it instead.

`V.or(rule1, ..., ruleN)`

tries to validate the value in focus with one of the
given rules starting from the first given rule. In case some rule accepts the
focus, that becomes the result of `V.or`

. Otherwise the error produced by the
last of the given rules becomes the result of `V.or`

. Note that `V.or`

is not
curried like `V.either`

.

Rules for validating elements can be lifted to rules for validating arrays of
elements.

All elements in a uniform array have the same form.

`V.arrayId(rule)`

validates the elements of an array with the given rule. In
case one or more elements are rejected, the error is an array containing only
the rejected elements.

The idea is that the elements of the validated array are addressed by some
unique identities intrinsic to the elements. Filtering out the accepted
elements keeps the error result readable.

`V.arrayIx(rule)`

validates the elements of an array with the given rule. In
case one or more elements are rejected, the error is an array containing the
rejected elements and `null`

values for the accepted elements.

The idea is that elements of the validated array are addressed only by their
index and it is necessary to keep the rejected elements at their original
indices. The accepted elements are replaced with `null`

to make the output less
noisy.

Elements at different positions in a varying array may have different forms.

`V.args(rule1, ..., ruleN)`

validates an array by validating each element of the
array with a specific rule. If the array is shorter than the number of rules,
the missing elements are treated as being `undefined`

and validated with the
corresponding rules. This means that rules for optional elements need to be
explicitly specified as such. If the array is longer than the number of rules,
the extra elements are simply accepted. This is roughly how JavaScript treats
function arguments. See also `V.tuple`

.

`V.tuple(rule1, ..., ruleN)`

validates a fixed length array by validating each
element of the array with a specific rule. See also `V.args`

.

For example:

```
V.accepts(
V.tuple(R.is(String), R.is(Number)),
['one', 2]
)
```

Note that elements cannot be removed from a tuple using `V.remove`

.

It is also possible to validate functions. Of course, validating a function is
different from validating data, because it is not possible to validate the
actual arguments to a function before the function is called and likewise it is
only possible to validate the return value of a function after the function
returns. Therefore validating a function means that the function is wrapped
with a function that performs validation of arguments and the return value as
the function is called.

`V.dependentFn(argumentsRule, argumentsToResultRule)`

wraps the
function at focus with a validating wrapper that validates the arguments to and
return value from the function as it is called. The rule for validating the
return value is constructed by calling the given function with the validated
arguments. In case there is no need for the return value rule to depend on the
actual arguments, one can use the simpler `V.freeFn`

combinator
instead.

For example:

```
const sqrt = V.validate(
V.dependentFn(
V.args(R.both(R.is(Number), R.lte(0))),
x => y => Math.abs(y*y - x) < 0.001
),
Math.sqrt
)
sqrt(4)
```

Note that the wrapped function produced by `V.dependentFn`

is not curried and
has zero arity. If necessary, you can wrap the produced function with
e.g. `R.curryN`

or
`R.nAry`

to change the arity of the function.

`V.freeFn(argumentsRule, resultRule)`

wraps the function at
focus with a validating wrapper that validates the arguments to and return value
from the function as it is called. `V.freeFn`

does not allow the rule for the
return value to depend on the arguments. If you wish to validate the return
value depending on the arguments you need to use the
`V.dependentFn`

combinator.

For example:

```
const random = V.validate(
V.freeFn(
V.tuple(),
R.both(R.lte(0), R.gt(1))
),
Math.random
)
random('Does not take arguments!')
```

Note that the wrapped function produced by `V.freeFn`

is not curried and has
zero arity. If necessary, you can wrap the produced function with
e.g. `R.curryN`

or
`R.nAry`

to change the arity of the function.

Rules for validating objects can be formed by composing rules for validating
individual properties of objects.

`V.keep('prop', rule)`

acts like the given rule except that in case the rule
rejects the focus, the specified property is copied from the original object to
the error object. This is useful when e.g. validating arrays of objects with an
identifying property. Keeping the identifying property allows the rejected
object to be identified.

`V.optional(rule)`

acts like the given rule except that in case the focus is
`undefined`

it is accepted without invoking the given rule. This is
particularly designed for specifying that an object property is optional.

For example:

```
V.validate(
V.arrayIx(
V.props({
field: V.optional([R.is(Number), 'Expected a number'])
})
),
[
{notTheField: []},
{field: 'Not a number'},
{field: 76}
]
)
```

`V.props({prop: rule, ...})`

is for validating an object and is given a template
object of rules with which to validate the corresponding fields. Unexpected
fields are rejected. Note that `V.props`

is equivalent to
`V.propsOr(V.reject)`

.

`V.propsOr(otherwise, {prop: rule, ...})`

is for validating an object and is
given a rule to apply to fields not otherwise specified and a template object of
rules with which to validate the corresponding fields. Note that
`V.props`

is equivalent to `V.propsOr(V.reject)`

.

Rules can be chosen conditionally on the data being validated.

`V.cases([p1, r1], ..., [pN, rN], [r])`

is given `[predicate, rule]`

-pairs as
arguments. The predicates are called from first to last with the focus. In
case a predicate passes, the corresponding rule is used on the focus and the
remaining predicates are skipped and rules ignored. The last argument to
`V.cases`

can be a default rule that omits the predicate, `[rule]`

, in which
case the rule is always applied in case no predicate passes. In case all
predicates fail and there is no default rule, the focus is rejected.

For example:

```
V.validate(
V.cases(
[
R.whereEq({type: 'a'}),
V.propsOr(V.accept, {
foo: [R.lt(0), 'Must be positive']
})
],
[
V.propsOr(V.accept, {
foo: [R.gt(0), 'Must be negative']
})
]
),
{
type: 'b',
foo: 10
}
)
```

Note that, like with `V.ifElse`

, `V.cases([p1, r1], ..., [rN])`

can
be expressed in terms of the logical operators, but `V.cases`

has a simpler
internal implementation and is likely to be faster.

`V.casesOf(traversal, [p1, r1], ..., [pN, rN], [r])`

is like
`V.cases`

except that subfocuses for the predicates are produced by
the given traversal from the current focus and a case is taken if the predicate
accepts any one of the subfocuses.

For example:

```
V.validate(
V.casesOf(
'type',
[R.identical('number'), V.props({type: R.is(String), value: R.is(Number)})],
[R.identical('string'), V.props({type: R.is(String), value: R.is(String)})]
),
{
type: 'string',
value: 'foo'
}
)
```

`V.ifElse(predicate, consequent, alternative)`

acts like the given consequent
rule in case the predicate is satisfied by the focus and otherwise like the
given alternative rule.

For example:

```
V.validate(
V.ifElse(R.is(Number), R.lte(0), R.is(String)),
-1
)
```

Note that `V.ifElse(p, c, a)`

can be expressed as `V.or(V.and(p, c), V.and(V.not(p), a))`

, but `V.ifElse`

has a simpler internal implementation and
is likely to be faster.

Rules can depend on the data being validated.

`V.choose(fn)`

is given a function that gets the current focus and then must
return rules to be used on the focus. This allows rules to depend on the data
and allows rules that examine multiple parts of the data.

For example:

```
V.validate(
V.choose(({a, b}) => V.props({
a: [R.equals(b), "Must equal 'b'"],
b: [R.equals(a), "Must equal 'a'"]
})),
{
a: 1,
b: 2
}
)
```

Note that `V.choose`

can be used to implement conditionals like
`V.cases`

and `V.ifElse`

. Also note that code inside
`V.choose`

, including code that constructs rules, is always run when the
`V.choose`

rule itself is used. For performance reasons it can be advantageous
to move invariant expressions outside of the body of the function given to
`V.choose`

. Also, when simpler conditional combinators like
`V.cases`

or `V.ifElse`

are sufficient, they can be
preferable for performance reasons, because they are given previously
constructed rules.

Rules for recursive data structures can be constructed with the help of
`V.choose`

and `V.lazy`

, which both allow one to refer
back to the rule itself or to delay the invocation of a rule computing function.

`V.lazy(fn)`

constructs a rule lazily. The given function is passed a
forwarding proxy to its own return value. This allows the rule to use itself as
a subrule and construct a recursive rule.

For example:

```
V.accepts(
V.lazy(tree => V.arrayId(
V.props({
name: R.is(String),
children: tree
})
)),
[
{
name: 'root',
children: [
{name: '1st child', children: []},
{
name: '2nd child',
children: [{name: 'You got the point', children: []}]
},
]
}
]
)
```

Rules can modify the value after a rule has accepted the focus.

Rules can include simple ad-hoc post-validation transformations.

`V.modifyAfter(rule, fn)`

replaces the focus after the given rule has accepted
it with the value returned by the given possibly async
function. `V.modifyAfter(rule, fn)`

is equivalent to `V.both(rule, V.acceptWith(fn))`

.

`V.setAfter(rule, value)`

replaces the focus after the given rule has accepted
it with the given value. `V.setAfter(rule, value)`

is equivalent to
`V.both(rule, V.acceptAs(value))`

.

`V.removeAfter(rule)`

removes the focus after the given rule has accepted it.
`V.removeAfter(rule)`

is equivalent to `V.both(rule, V.remove)`

.

Rules can validate versioned data and transform it to another version in the
process. The combinators `V.promote`

,
`V.upgrades`

, `V.upgradesOf`

are designed for
cases where there are multiple versions of data or schema. Using them one can
validate any one of the versions and also convert the data to desired version
— usually to the latest version — so that rest of the program does
not need to deal with different versions.

`V.promote`

is like `V.or`

, but the rules given to `V.promote`

need to
be wrapped inside an array `[rule]`

and may optionally include a transformation
function, `[rule, fn]`

. `V.promote`

tries, like `V.or`

, to find a rule
that accepts the focus. If no such rule is found, the focus is rejected.
Otherwise if the accepting rule has an associated function, then the function is
used to transform the focus and the same validation process is rerun. This way
any sequence of transformations is also validated.

For example:

```
V.validate(
V.promote(
[
V.props({
type: R.identical('v2'),
value: R.is(Number)
})
],
[
V.props({
type: R.identical('v1'),
constant: R.is(Number)
}),
({constant}) => ({type: 'v2', value: constant})
]
),
{type: 'v1', constant: 42}
)
```

Note that `V.or(r1, ..., rN)`

is equivalent to `V.promote([r1], ..., [rN])`

.

`V.upgrades`

is like `V.cases`

, but each case may optionally include
a transformation function, `[predicate, rule, fn]`

. `V.upgrades`

tries, like
`V.cases`

, to find the first passing predicate. When no such
predicate is found, the focus is rejected. Otherwise the focus is validated
with the associated rule. If the case also includes a possibly
async transformation function, the function is used to transform
the value in focus and the same validation process is rerun. This way any
sequence of transformations is also validated.

For example:

```
V.validate(
V.upgrades(
[
L.get(['type', R.identical('v1')]),
V.props({
type: R.is(String),
constant: R.is(Number)
}),
({constant}) => ({type: 'v2', value: constant})
],
[
L.get(['type', R.identical('v2')]),
V.props({
type: R.is(String),
value: R.is(Number)
})
]
),
{type: 'v1', constant: 42}
)
```

Note that `V.cases([p1, r1], ..., [[pN, ]rN])`

is equivalent to `V.upgrades([p1, r1], ..., [[pN, ]rN])`

.

`V.upgradesOf`

is like `V.casesOf`

, but each case may optionally
include a transformation function, `[predicate, rule, fn]`

. `V.upgradesOf`

tries, like `V.casesOf`

, to find the first predicate that accepts
any one of the the traversed subfocuses. When no such predicate is found, the
focus is rejected. Otherwise the focus is validated with the associated rule.
If the case also includes a possibly async transformation
function, the function is used to transform the value in focus and the same
validation process is rerun. This way any sequence of transformations is also
validated.

For example:

```
V.validate(
V.upgradesOf(
'type',
[
R.identical('v1'),
V.props({
type: R.is(String),
constant: R.is(Number)
}),
({constant}) => ({type: 'v2', value: constant})
],
[
R.identical('v2'),
V.props({
type: R.is(String),
value: R.is(Number)
})
]
),
{type: 'v1', constant: 42}
)
```

Note that `V.casesOf(t, [p1, r1], ..., [[pN, ]rN])`

is equivalent to
`V.upgradesOf(t, [p1, r1], ..., [[pN, ]rN])`

.

The following subsections give some tips on effective use of this library.

The logical combinators `V.or`

, `V.either`

, and also
`V.promote`

, can be convenient, but it is often preferable to use
conditional combinators like `V.cases`

and
`V.upgrades`

, because, once a case predicate has been satisfied,
no other cases are attempted in case the corresponding rule fails and the
resulting error is likely to be of higher quality.

It might be tempting to use `V.and`

to combine
`V.propsOr(V.accept)`

rules

```
V.and(
V.propsOr(V.accept, rules1),
V.propsOr(V.accept, rules2),
)
```

but this has a couple of disadvantages:

- The resulting rule will accept additional properties.
- If one of the
`V.propsOr`

rules rejects, then errors from later rules are not
reported.

It is usually better to combine rule templates inside `V.props`

instead:

```
V.props({
...rules1,
...rules2,
})
```

This way additional properties are not accepted and errors from all rules are
reported in case of rejection.

Probably the main weakness in the design of this library is that this library
specifically tries to avoid having to implement everything. In particular, one
of the ideas is to simply allow arbitrary predicates from a library like
Ramda to be used as rules. This means that rules do not
contain extra information such as a corresponding random value
generator of values matching the rule
or a traversable specification of the rule for exporting the specification for
external tools. One way
to provide such features is to pair validation rules with the necessary extra
information. It should be possible to do that outside of this library.

The current implementation does not operate
incrementally. Every
time e.g. `V.validate`

is called, everything is recomputed. This
can become a performance issue particularly in an interactive setting where
small incremental changes to a data structure are being validated in response to
user actions. It should be possible to implement caching so that on repeated
calls only changes would be recomputed. This is left for future work.

This library primarily exists as a result of Stefan Rimaila's work on
validation using lenses.