Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

revalidate

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

revalidate - npm Package Compare versions

Comparing version 0.4.1 to 1.0.0

immutable.js

111

CHANGELOG.md

@@ -0,1 +1,112 @@

## v1.0.0
### :tada: First major release - NO breaking changes
Revalidate has been out for several months, and the API has stayed pretty solid.
With the addition of Immutable.js and arbitrary data source support along with
docs and Flow typechecking, I feel like revalidate is ready to be bumped to v1.
A couple internal helpful error messages were removed for being redundant or
unnecessary, but there weren't any real breaking changes in this release.
Therefore, you should be to upgrade with no problem.
### NEW - Immutable.js support
Revalidate now supports Immutable.js data structures for holding form values.
Simply import `combineValidators` from `revalidate/immutable` instead.
```js
// ES2015
import {
createValidator,
composeValidators,
isRequired,
isAlphabetic,
isNumeric
} from 'revalidate';
import { combineValidators } from 'revalidate/immutable';
import { Map } from 'immutable';
// Or ES5
var r = require('revalidate');
var combineValidators = require('revalidate/immutable').combineValidators;
var createValidator = r.createValidator;
var composeValidators = r.composeValidators;
var isRequired = r.isRequired;
var isAlphabetic = r.isAlphabetic;
var isNumeric = r.isNumeric;
const dogValidator = combineValidators({
name: composeValidators(
isRequired,
isAlphabetic
)('Name'),
age: isNumeric('Age')
});
dogValidator(Map()); // { name: 'Name is required' }
dogValidator(Map({ name: '123', age: 'abc' }));
// { name: 'Name must be alphabetic', age: 'Age must be numeric' }
dogValidator(Map({ name: 'Tucker', age: '10' })); // {}
```
### NEW - Arbitrary data sources
In fact, Immutable.js support is built upon a general method for using any data
source for form values. To use other data sources, simply supply a
`serializeValues` option to `combineValidators`. The example below wraps form
values with a thunk.
```js
// ES2015
import {
createValidator,
combineValidators,
composeValidators,
isRequired,
isAlphabetic,
isNumeric
} from 'revalidate';
// Or ES5
var r = require('revalidate');
var createValidator = r.createValidator;
var combineValidators = r.combineValidators;
var composeValidators = r.composeValidators;
var isRequired = r.isRequired;
var isAlphabetic = r.isAlphabetic;
var isNumeric = r.isNumeric;
const dogValidator = combineValidators({
name: composeValidators(
isRequired,
isAlphabetic
)('Name'),
age: isNumeric('Age')
}, {
// Values are wrapped with a function.
// NOTE: our simple wrapper would only work for shallow field values.
serializeValues: values => values(),
});
dogValidator(() => ({})); // { name: 'Name is required' }
dogValidator(() => ({ name: '123', age: 'abc' }));
// { name: 'Name must be alphabetic', age: 'Age must be numeric' }
dogValidator(() => ({ name: 'Tucker', age: '10' })); // {}
```
### Miscellaneous
- Add Flow typing
- Internal cleanup
- Migrate tests to Jest
- 100% code coverage!
## v0.4.1

@@ -2,0 +113,0 @@

13

lib/assertions.js

@@ -41,3 +41,2 @@ 'use strict';

}
function hasErrorAt(result, key) {

@@ -48,2 +47,6 @@ if (result == null || typeof result !== 'object') {

if (key == null) {
throw new Error('Please provide a key to check for an error.');
}
return hasError((0, _get2.default)(result, key));

@@ -53,2 +56,10 @@ }

function hasErrorOnlyAt(result, key) {
if (result == null || typeof result !== 'object') {
return false;
}
if (key == null) {
throw new Error('Please provide a key to check for an error.');
}
var omitted = (0, _cloneDeep2.default)(result);

@@ -55,0 +66,0 @@

6

lib/combineValidators.js

@@ -16,5 +16,5 @@ 'use strict';

function combineValidators(validators) {
var finalValidators = (0, _ensureNestedValidators2.default)(validators);
return (0, _internalCombineValidators2.default)(finalValidators, true);
function combineValidators(validators, options) {
var finalValidators = (0, _ensureNestedValidators2.default)(validators, options);
return (0, _internalCombineValidators2.default)(finalValidators, true, options);
}

@@ -28,5 +28,5 @@ 'use strict';

function composeValidators() {
for (var _len = arguments.length, validators = Array(_len), _key = 0; _key < _len; _key++) {
validators[_key] = arguments[_key];
function composeValidators(firstValidator) {
for (var _len = arguments.length, validators = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
validators[_key - 1] = arguments[_key];
}

@@ -44,7 +44,11 @@

if (config.multiple === true) {
return (0, _markAsValueValidator2.default)((0, _createValidatorWithMultipleErrors2.default)(validators.slice(0), (0, _omit2.default)(config, 'multiple')));
return (0, _markAsValueValidator2.default)((0, _createValidatorWithMultipleErrors2.default)(firstValidator, validators.slice(0), (0, _omit2.default)(config, 'multiple')));
}
return (0, _markAsValueValidator2.default)((0, _createValidatorWithSingleError2.default)(validators.slice(0), config));
if (typeof firstValidator === 'object') {
throw new Error('Please only pass in functions when composing ' + 'validators to produce a single error message.');
}
return (0, _markAsValueValidator2.default)((0, _createValidatorWithSingleError2.default)([firstValidator].concat(validators), config));
};
}

@@ -6,10 +6,2 @@ 'use strict';

var _invariant = require('invariant');
var _invariant2 = _interopRequireDefault(_invariant);
var _isPlainObject = require('lodash/isPlainObject');
var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
var _markAsValueValidator = require('./internal/markAsValueValidator');

@@ -21,30 +13,36 @@

function createValidator(curriedDefinition, defaultMessageCreator) {
var messageCreatorIsString = typeof defaultMessageCreator === 'string';
function getMessage(config, defaultMessageCreator) {
if (typeof config === 'object' && config != null) {
if (typeof config.message === 'string') {
return config.message;
}
(0, _invariant2.default)(messageCreatorIsString || typeof defaultMessageCreator === 'function', 'Please provide a message string or message creator function');
if (typeof defaultMessageCreator === 'string') {
return defaultMessageCreator;
}
return function validator(config, value, allValues) {
var configIsObject = (0, _isPlainObject2.default)(config);
if (typeof config.field === 'string') {
return defaultMessageCreator(config.field);
}
}
if (!messageCreatorIsString) {
(0, _invariant2.default)(typeof config === 'string' || configIsObject, 'Please provide a string or configuration object with a `field` or ' + '`message` property');
if (typeof defaultMessageCreator === 'string') {
return defaultMessageCreator;
}
if (configIsObject) {
(0, _invariant2.default)('field' in config || 'message' in config, 'Please provide a `field` or `message` property');
}
}
if (typeof config === 'string') {
return defaultMessageCreator(config);
}
var message = void 0;
throw new Error('Please provide a string or configuration object with a `field` or ' + '`message` property');
}
function createValidator(curriedDefinition, defaultMessageCreator) {
if (defaultMessageCreator == null || typeof defaultMessageCreator !== 'string' && typeof defaultMessageCreator !== 'function') {
throw new Error('Please provide a message string or message creator function');
}
if (configIsObject && 'message' in config) {
message = config.message;
} else if (messageCreatorIsString) {
message = defaultMessageCreator;
} else if (configIsObject) {
message = defaultMessageCreator(config.field);
} else {
message = defaultMessageCreator(config);
}
var finalMessageCreator = defaultMessageCreator;
return function validator(config, value, allValues) {
var message = getMessage(config, finalMessageCreator);
var valueValidator = curriedDefinition(message);

@@ -51,0 +49,0 @@

@@ -12,48 +12,37 @@ 'use strict';

function buildErrorsArray(validators, validate) {
return validators.reduce(function (errors, validator) {
var errorMessage = validate(validator);
function validateWithValidator(value, allValues, sharedConfig, validator) {
if ((0, _isValueValidator2.default)(validator)) {
return validator(value, allValues);
}
if (errorMessage) {
errors.push(errorMessage);
}
return errors;
}, []);
return validator(sharedConfig, value, allValues);
}
function createValidatorWithMultipleErrors(firstValidator, validators, sharedConfig) {
if (typeof firstValidator === 'object') {
return function composedValidator(value, allValues) {
return Object.keys(firstValidator).reduce(function (errors, key) {
var validator = firstValidator[key];
function buildErrorsObject(validators, validate) {
return Object.keys(validators).reduce(function (errors, key) {
var validator = validators[key];
var errorMessage = validate(validator);
var errorMessage = validateWithValidator(value, allValues, sharedConfig, validator);
if (errorMessage) {
errors[key] = errorMessage;
}
if (errorMessage) {
errors[key] = errorMessage;
}
return errors;
}, {});
}
function createValidatorWithMultipleErrors(validators, sharedConfig) {
var buildErrors = void 0;
var finalValidators = void 0;
if (typeof validators[0] === 'object') {
buildErrors = buildErrorsObject;
finalValidators = validators[0];
} else {
buildErrors = buildErrorsArray;
finalValidators = validators;
return errors;
}, {});
};
}
return function composedValidator(value, allValues) {
return buildErrors(finalValidators, function (validator) {
if ((0, _isValueValidator2.default)(validator)) {
return validator(value, allValues);
return [firstValidator].concat(validators).reduce(function (errors, validator) {
var errorMessage = validateWithValidator(value, allValues, sharedConfig, validator);
if (errorMessage) {
errors.push(errorMessage);
}
return validator(sharedConfig, value, allValues);
});
return errors;
}, []);
};
}

@@ -20,3 +20,3 @@ 'use strict';

function ensureNestedValidators(validators) {
function ensureNestedValidators(validators, options) {
var baseShape = Object.keys(validators).reduce(function (root, path) {

@@ -26,3 +26,3 @@ return (0, _objectAssign2.default)({}, root, (0, _fillObjectFromPath2.default)(root, path.split('.'), validators[path]));

return (0, _internalCombineNestedValidators2.default)(baseShape);
return (0, _internalCombineNestedValidators2.default)(baseShape, options);
}

@@ -12,6 +12,6 @@ 'use strict';

function internalCombineNestedValidators(baseShape) {
function internalCombineNestedValidators(baseShape, options) {
return Object.keys(baseShape).reduce(function (memo, key) {
if (typeof baseShape[key] === 'object') {
memo[key] = (0, _internalCombineValidators2.default)(internalCombineNestedValidators(baseShape[key]));
memo[key] = (0, _internalCombineValidators2.default)(internalCombineNestedValidators(baseShape[key], options), false, options);
} else {

@@ -18,0 +18,0 @@ memo[key] = baseShape[key];

@@ -12,14 +12,27 @@ 'use strict';

function internalCombineValidators(validators) {
var atRoot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
function defaultSerializeValues(values) {
return values;
}
function internalCombineValidators(validators, atRoot) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
return function valuesValidator() {
var values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var allValues = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var serializeValues = atRoot && typeof options.serializeValues === 'function' ? options.serializeValues : defaultSerializeValues;
function finalSerializeValues(values) {
if (values == null) {
return {};
}
return serializeValues(values) || {};
}
return function valuesValidator(values, allValues) {
var serializedValues = finalSerializeValues(values);
var serializedAllValues = finalSerializeValues(allValues);
return Object.keys(validators).reduce(function (errors, fieldName) {
var parsedField = (0, _parseFieldName2.default)(fieldName);
var validator = validators[parsedField.fullName];
var value = values[parsedField.baseName];
var finalAllValues = atRoot ? values : allValues;
var value = serializedValues[parsedField.baseName];
var finalAllValues = atRoot ? serializedValues : serializedAllValues;

@@ -26,0 +39,0 @@ var errorMessage = parsedField.isArray ? (value || []).map(function (fieldValue) {

@@ -12,2 +12,2 @@ 'use strict';

var VALUE_VALIDATOR_SYMBOL = exports.VALUE_VALIDATOR_SYMBOL = (0, _sym2.default)('VALUE_VALIDATOR'); /* eslint-disable import/prefer-default-export */
var VALUE_VALIDATOR_SYMBOL = exports.VALUE_VALIDATOR_SYMBOL = (0, _sym2.default)('VALUE_VALIDATOR');

@@ -6,6 +6,2 @@ 'use strict';

var _invariant = require('invariant');
var _invariant2 = _interopRequireDefault(_invariant);
var _createValidator = require('../createValidator');

@@ -22,4 +18,2 @@

function isRequiredIf(condition) {
(0, _invariant2.default)(typeof condition === 'function', 'Please provide a condition function to determine if a field should be required');
return (0, _createValidator2.default)(function (message) {

@@ -26,0 +20,0 @@ return function (value, allValues) {

@@ -13,5 +13,7 @@ 'use strict';

function matchesPattern(regex) {
var regexString = regex.toString();
return (0, _internalMatchesPattern2.default)(regex, function (field) {
return field + ' must match pattern ' + regex;
return field + ' must match pattern ' + regexString;
});
}
{
"name": "revalidate",
"version": "0.4.1",
"version": "1.0.0",
"description": "Elegant and composable validations",

@@ -9,11 +9,19 @@ "main": "lib/index.js",

"lib",
"assertions.js"
"assertions.js",
"immutable.js"
],
"scripts": {
"build": "babel src --out-dir lib",
"check": "npm run lint && npm run typecheck && npm test",
"clean": "rimraf lib",
"lint": "eslint src/ test/",
"lint": "eslint src __tests__",
"prepublish": "npm run clean && npm run build",
"test": "ava",
"watch:test": "ava -w"
"test": "jest",
"typecheck": "flow",
"watch:test": "jest --watch",
"docs:clean": "rimraf _book",
"docs:prepare": "gitbook install",
"docs:build": "npm run docs:prepare && gitbook build",
"docs:watch": "npm run docs:prepare && gitbook serve",
"docs:publish": "npm run docs:clean && npm run docs:build && cp CNAME _book && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:jfairbank/revalidate gh-pages --force"
},

@@ -37,6 +45,6 @@ "repository": {

"devDependencies": {
"ava": "^0.16.0",
"babel-cli": "^6.9.0",
"babel-core": "^6.9.0",
"babel-eslint": "^7.0.0",
"babel-jest": "^16.0.0",
"babel-plugin-check-es2015-constants": "^6.8.0",

@@ -52,2 +60,3 @@ "babel-plugin-transform-es2015-arrow-functions": "^6.8.0",

"babel-plugin-transform-export-extensions": "^6.8.0",
"babel-plugin-transform-flow-strip-types": "^6.14.0",
"babel-plugin-transform-object-rest-spread": "^6.8.0",

@@ -57,97 +66,30 @@ "babel-plugin-transform-runtime": "^6.9.0",

"babel-runtime": "^6.11.6",
"eslint": "^3.2.2",
"eslint-config-airbnb-base": "^8.0.0",
"eslint": "^3.8.0",
"eslint-config-airbnb-base": "^9.0.0",
"eslint-import-resolver-node": "^0.2.0",
"eslint-plugin-import": "^1.12.0",
"eslint-plugin-flowtype": "^2.20.0",
"eslint-plugin-import": "^2.0.1",
"flow-bin": "^0.33.0",
"gitbook-cli": "^2.3.0",
"gitbook-plugin-advanced-emoji": "^0.2.1",
"immutable": "^3.8.1",
"jest": "^16.0.1",
"rimraf": "^2.5.4"
},
"dependencies": {
"invariant": "^2.2.1",
"lodash": "^4.15.0",
"object-assign": "^4.1.0"
},
"babel": {
"plugins": [
"check-es2015-constants",
"transform-es2015-arrow-functions",
"transform-es2015-block-scoping",
[
"transform-es2015-computed-properties",
{
"loose": true
}
],
[
"transform-es2015-destructuring",
{
"loose": true
}
],
[
"transform-es2015-modules-commonjs",
{
"loose": true
}
],
"transform-es2015-parameters",
"transform-es2015-shorthand-properties",
"transform-es2015-template-literals",
"transform-object-rest-spread",
[
"transform-runtime",
{
"polyfill": false,
"regenerator": false
}
],
"transform-export-extensions"
]
},
"eslintConfig": {
"parser": "babel-eslint",
"extends": [
"airbnb-base",
"plugin:import/errors",
"plugin:import/warnings"
"jest": {
"collectCoverage": true,
"collectCoverageFrom": [
"src/**/*.js"
],
"settings": {
"import/resolver": "node"
},
"rules": {
"arrow-parens": 0,
"consistent-return": 0,
"no-param-reassign": [
"error",
{
"props": false
}
],
"no-plusplus": 0,
"no-unused-vars": [
"error",
{
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"quote-props": [
"error",
"consistent"
],
"prefer-rest-params": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true
}
]
}
},
"ava": {
"require": "babel-register",
"source": [
"**/*.js",
"!**/*.swp"
]
"coverageReporters": [
"json",
"lcov",
"text-summary"
],
"testRegex": "__tests__/.*\\.test\\.js$"
}
}
# <img src="https://raw.githubusercontent.com/jfairbank/revalidate/master/logo/logo.png" width="350" alt="revalidate">
[![Analytics](https://ga-beacon.appspot.com/UA-52148605-6/revalidate?pixel)](https://github.com/jfairbank/revalidate)
[![npm](https://img.shields.io/npm/v/revalidate.svg?style=flat-square)](https://www.npmjs.com/package/revalidate)
[![Travis branch](https://img.shields.io/travis/jfairbank/revalidate/master.svg?style=flat-square)](https://travis-ci.org/jfairbank/revalidate)
[![npm](https://img.shields.io/npm/v/revalidate.svg?style=flat-square)](https://www.npmjs.com/package/revalidate)
[![Codecov](https://img.shields.io/codecov/c/github/jfairbank/revalidate.svg?style=flat-square)](https://codecov.io/gh/jfairbank/revalidate)
Elegant and composable validations.
#### Elegant and composable validations.
Revalidate was originally created as a helper library for composing and reusing
common validations to generate validate functions for
[Redux Form](https://github.com/erikras/redux-form). It became evident that the
validators that revalidate can generate are pretty agnostic about how they are
used. They are just functions that take a value and return an error message if
the value is invalid.
Revalidate is a library for creating and composing together small validation
functions to create complex, robust validations. There is no need for awkward
configuration rules to define validations. Just use functions.
## Table of Contents
All right. No more upselling. Just look at an example :heart:.
- [Install](#install)
- [Integrations](#tada-integrations-tada)
- [Usage](#usage)
- [Common Validators](#common-validators)
- [Test Helpers](#test-helpers)
## Install
$ npm install revalidate
## :tada: Integrations :tada:
- [react-revalidate](https://github.com/jfairbank/react-revalidate)<br>
Validate React component props with revalidate validation functions.
- [redux-revalidate](https://github.com/jfairbank/redux-revalidate)<br>
Validate your Redux store state with revalidate validation functions.
- [Redux Form](https://github.com/erikras/redux-form)<br>
Create validation functions for your form components out of the box. See the
[example below](#redux-form).
## Usage
Revalidate provides functions for creating validation functions as well as
composing and combining them. Think [redux](https://github.com/reactjs/redux)
for validation functions.
### `createValidator`
The simplest function is `createValidator` which creates a value validation
function. `createValidator` takes two arguments. The first argument is a curried
function that takes an error message and the value. The curried function must
return the message if the value is invalid. If the field value is valid, it's
recommended that you return nothing, so a return value of `undefined` implies
the field value was valid.
The second argument is a function that takes a field name and must return the
error message. Optionally, you can just pass in a string as the second argument
if you don't want to depend on the field name.
The returned validation function is also a curried function. The first argument
is a field name string or a configuration object where you can specify the field
or a custom error message. The second argument is the value. You can pass in
both arguments at the same time too. We'll see why currying the function can be
useful when we want to compose validators.
Here is an implementation of an `isRequired` validator with `createValidator`:
```js
// ES2015 - import and define validator
import { createValidator } from 'revalidate';
const isRequired = createValidator(
message => value => {
if (value == null || value === '') {
return message;
}
},
field => `${field} is required`
);
// Or ES5 - require and define validator
var createValidator = require('revalidate').createValidator;
var isRequired = createValidator(
function(message) {
return function(value) {
if (value == null || value === '') {
return message;
}
};
},
function(field) {
field + ' is required'
}
);
// Using validator
isRequired('My Field')(); // 'My Field is required'
isRequired('My Field')(''); // 'My Field is required'
isRequired('My Field')('42'); // undefined, therefore assume valid
// With a custom message
isRequired({ message: 'Error' })(); // 'Error'
```
Validation functions can optionally accept a second parameter including all of
the current values. This allows comparing one value to another as part of
validation. For example:
```js
// ES2015
import { createValidator } from 'revalidate';
// Or ES5
var createValidator = require('revalidate').createValidator;
export default function matchesField(otherField, otherFieldLabel) {
return createValidator(
message => (value, allValues) => {
if (!allValues || value !== allValues[otherField]) {
return message;
}
},
field => `${field} must match ${otherFieldLabel}`
);
}
matchesField('password')('My Field')();
// 'My Field does not match'
matchesField('password')('My Field')('yes', { password: 'no' });
// 'My Field does not match'
matchesField('password')('My Field')('yes', { password: 'yes' });
// undefined, therefore assume valid
// With a custom message
matchesValue('password')({
message: 'Passwords must match',
})('yes', { password: 'no' }); // 'Passwords must match'
```
---
### `composeValidators`
Revalidate becomes really useful when you use the `composeValidators` function.
As the name suggests, it allows you to compose validators into one. By default
the composed validator will check each validator and return the first error
message it encounters. Validators are checked in a left-to-right fashion to
make them more readable. (**Note:** this is opposite most functional
implementations of the compose function.)
The composed validator is also curried and takes the same arguments as an
individual validator made with `createValidator`.
```js
// ES2015
import {
createValidator,
composeValidators,
isRequired
} from 'revalidate';
// Or ES5
var r = require('revalidate');
var createValidator = r.createValidator;
var composeValidators = r.composeValidators;
var isRequired = r.isRequired;
// Usage
const isAlphabetic = createValidator(
message => value => {
if (value && !/^[A-Za-z]+$/.test(value)) {
return message;
}
},
field => `${field} must be alphabetic`
);
const validator = composeValidators(
isRequired,
// You can still customize individual validators
// because they're curried!
isAlphabetic({
message: 'Can only contain letters'
})
)('My Field');
validator(); // 'My Field is required'
validator('123'); // 'Can only contain letters'
validator('abc'); // undefined
```
#### Multiple Errors as an Array
You can supply an additional `multiple: true` option to return all errors as an
array from your composed validators. This will run all composed validations
instead of stopping at the first one that fails.
```js
// ES2015
import { createValidator, composeValidators } from 'revalidate';
// Or ES5
var r = require('revalidate');
var createValidator = r.createValidator;
var composeValidators = r.composeValidators;
// Usage
const startsWithA = createValidator(
message => value => {
if (value && !/^A/.test(value)) {
return message;
}
},
field => `${field} must start with A`
);
const endsWithC = createValidator(
message => value => {
if (value && !/C$/.test(value)) {
return message;
}
},
field => `${field} must end with C`
);
const validator = composeValidators(
startsWithA,
endsWithC
)({ field: 'My Field', multiple: true });
validator('BBB');
// [
// 'My Field must start with A',
// 'My Field must end with C'
// ]
```
#### Multiple Errors as an Object
Alternatively, if you want to be able to reference specific errors, you can
return multiple errors as an object, thereby allowing you to name the errors. To
return multiple errors as an object, pass in your validators as an object to
`composeValidators` instead of a variadic number of arguments. The keys you use
in your object will be the keys in the returned errors object. Don't forget to
still supply the `multiple: true` option!
```js
const validator = composeValidators({
A: startsWithA,
C: endsWithC
})({ field: 'My Field', multiple: true });
validator('BBB');
// {
// A: 'My Field must start with A',
// C: 'My Field must end with C'
// }
```
---
### `combineValidators`
`combineValidators` is analogous to a function like `combineReducers` from
redux. It allows you to validate multiple field values at once. It returns a
function that takes an object with field names mapped to their values.
`combineValidators` will run named validators you supplied it with their
respective field values and return an object literal containing any error
messages for each field value. An empty object return value implies no field
values were invalid.
```js
// ES2015
import {
createValidator,
composeValidators,
combineValidators,

@@ -301,3 +37,2 @@ isRequired,

const dogValidator = combineValidators({
// Use composeValidators too!
name: composeValidators(

@@ -308,4 +43,2 @@ isRequired,

// Don't forget to supply a field name if you
// don't compose other validators
age: isNumeric('Age')

@@ -322,729 +55,16 @@ });

---
## Install
### Nested Fields
Install with yarn or npm.
`combineValidators` also works with deeply nested fields in objects and arrays.
`yarn add revalidate`
To specify nested fields, just supply the path to the field with dots:
`'contact.firstName'`.
`npm install --save revalidate`
For arrays of values you can use brace syntax: `'phones[]'`.
## Getting Started
For nested fields of objects in arrays you can combine dots and braces:
`'cars[].make'`.
#### [Docs](http://revalidate.jeremyfairbank.com)
You can combine and traverse as deep as you want:
`'deeply.nested.list[].of.cats[].name'`!
```js
// ES2015
import {
composeValidators,
combineValidators,
isRequired,
isAlphabetic,
isNumeric,
isOneOf,
matchesField,
} from 'revalidate';
// Or ES5
var r = require('revalidate');
var composeValidators = r.composeValidators;
var combineValidators = r.combineValidators;
var isRequired = r.isRequired;
var isAlphabetic = r.isAlphabetic;
var isNumeric = r.isNumeric;
var isOneOf = r.isOneOf;
var matchesField = r.matchesField;
// Usage
const validate = combineValidators({
// Shallow fields work with nested fields still
'favoriteMeme': isAlphabetic('Favorite Meme'),
// Specify fields of nested object
'contact.name': composeValidators(
isRequired,
isAlphabetic
)('Contact Name'),
'contact.age': isNumeric('Contact Age'),
// Specify array of string values
'phones[]': isNumeric('Phone'),
// Specify nested fields of arrays of objects
'cars[].make': composeValidators(
isRequired,
isOneOf(['Honda', 'Toyota', 'Ford'])
)('Car Make'),
// Match other nested field values
'otherContact.name': matchesField(
'contact.name',
'Contact Name'
)('Other Name'),
});
// Empty values
validate({});
// Empty arrays for phones and cars because no nested fields or values
// to be invalid. Message for required name on contact still shows up.
//
// { contact: { name: 'Contact Name is required' },
// phones: [],
// cars: [],
// otherContact: {} }
// Invalid/missing values
validate({
contact: { name: 'Joe', age: 'thirty' }, // Invalid age
phones: ['abc', '123'], // First phone invalid
cars: [{ make: 'Toyota' }, {}], // Second car missing make
otherContact: { name: 'Jeremy' }, // Names don't match
});
// Notice that array error messages match by index. For valid
// nested objects in arrays, you get get back an empty object
// for the index. For valid string values in arrays, you get
// back undefined for the index.
//
// { contact: { age: 'Contact Age must be numeric' },
// phones: ['Phone must be numeric', undefined],
// cars: [{}, { make: 'Car Make is required' }],
// otherContact: { name: 'Other Name must match Contact Name' } }
```
---
### Redux Form
As mentioned, even though revalidate is pretty agnostic about how you use it, it
does work out of the box for Redux Form. The `validate` function you might write
for a Redux Form example like
[here](http://redux-form.com/5.3.3/#/examples/synchronous-validation?_k=mncrmp)
can also be automatically generated with `combineValidators`. The function it
returns will work perfectly for the `validate` option for your form components
for React and Redux Form.
Here is that example from Redux Form rewritten to generate a `validate` function
with revalidate.
```js
import React, {Component, PropTypes} from 'react';
import {reduxForm} from 'redux-form';
import {
createValidator,
composeValidators,
combineValidators,
isRequired,
hasLengthLessThan,
isNumeric
} from 'revalidate';
export const fields = ['username', 'email', 'age'];
const isValidEmail = createValidator(
message => value => {
if (value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
return message;
}
},
'Invalid email address'
);
const isGreaterThan = (n) => createValidator(
message => value => {
if (value && Number(value) <= n) {
return message;
}
},
field => `${field} must be greater than ${n}`
);
const customIsRequired = isRequired({ message: 'Required' });
const validate = combineValidators({
username: composeValidators(
customIsRequired,
hasLengthLessThan(16)({
message: 'Must be 15 characters or less'
})
)(),
email: composeValidators(
customIsRequired,
isValidEmail
)(),
age: composeValidators(
customIsRequired,
isNumeric({
message: 'Must be a number'
}),
isGreaterThan(17)({
message: 'Sorry, you must be at least 18 years old'
})
)()
});
class SynchronousValidationForm extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired
};
render() {
const {fields: {username, email, age}, resetForm, handleSubmit, submitting} = this.props;
return (<form onSubmit={handleSubmit}>
<div>
<label>Username</label>
<div>
<input type="text" placeholder="Username" {...username}/>
</div>
{username.touched && username.error && <div>{username.error}</div>}
</div>
<div>
<label>Email</label>
<div>
<input type="text" placeholder="Email" {...email}/>
</div>
{email.touched && email.error && <div>{email.error}</div>}
</div>
<div>
<label>Age</label>
<div>
<input type="text" placeholder="Age" {...age}/>
</div>
{age.touched && age.error && <div>{age.error}</div>}
</div>
<div>
<button type="submit" disabled={submitting}>
{submitting ? <i/> : <i/>} Submit
</button>
<button type="button" disabled={submitting} onClick={resetForm}>
Clear Values
</button>
</div>
</form>
);
}
}
export default reduxForm({
form: 'synchronousValidation',
fields,
validate
})(SynchronousValidationForm);
```
## Common Validators
Revalidate exports some common validations for your convenience. If you need
something more complex, then you'll need to create your own validators with
`createValidator`.
- [`isRequired`](#isrequired)
- [`hasLengthBetween`](#haslengthbetween)
- [`hasLengthGreaterThan`](#haslengthgreaterthan)
- [`hasLengthLessThan`](#haslengthlessthan)
- [`isAlphabetic`](#isalphabetic)
- [`isAlphaNumeric`](#isalphanumeric)
- [`isNumeric`](#isnumeric)
- [`isOneOf`](#isoneof)
- [`matchesField`](#matchesfield)
- [`isRequiredIf`](#isrequiredif)
- [`matchesPattern`](#matchespattern)
### `isRequired`
`isRequired` is pretty self explanatory. It determines that a value isn't valid
if it's `null`, `undefined` or the empty string `''`.
```js
isRequired('My Field')(); // 'My Field is required'
isRequired('My Field')(null); // 'My Field is required'
isRequired('My Field')(''); // 'My Field is required'
isRequired('My Field')('42'); // undefined, therefore assume valid
```
### `hasLengthBetween`
`hasLengthBetween` tests that the value falls between a min and max inclusively.
It wraps a call to `createValidator`, so you must first call it with the min and
max arguments.
```js
hasLengthBetween(1, 3)('My Field')('hello');
// 'My Field must be between 1 and 3 characters long'
```
### `hasLengthGreaterThan`
`hasLengthGreaterThan` tests that the value is greater than a predefined length.
It wraps a call to `createValidator`, so you must first call it with the
min length.
```js
hasLengthGreaterThan(3)('My Field')('foo');
// 'My Field must be longer than 3 characters'
```
### `hasLengthLessThan`
`hasLengthLessThan` tests that the value is less than a predefined length. It
wraps a call to `createValidator`, so you must first call it with the max
length.
```js
hasLengthLessThan(4)('My Field')('hello');
// 'My Field cannot be longer than 4 characters'
```
### `isAlphabetic`
`isAlphabetic` simply tests that the value only contains any of the 26 letters
in the English alphabet.
```js
isAlphabetic('My Field')('1');
// 'My Field must be alphabetic'
```
### `isAlphaNumeric`
`isAlphaNumeric` simply tests that the value only contains any of the 26 letters
in the English alphabet or any numeric digit (i.e. 0-9).
```js
isAlphaNumeric('My Field')('!@#$');
// 'My Field must be alphanumeric'
```
### `isNumeric`
`isNumeric` simply tests that the **string** is comprised of only digits (i.e.
0-9).
```js
isNumeric('My Field')('a');
// 'My Field must be numeric'
```
### `isOneOf`
`isOneOf` tests that the value is contained in a predefined array of values. It
wraps a call to `createValidator`, so you must first call it with the array of
allowed values.
```js
isOneOf(['foo', 'bar'])('My Field')('baz');
// 'My Field must be one of ["foo","bar"]'
isOneOf(['foo', 'bar'])('My Field')('FOO');
// 'My Field must be one of ["foo","bar"]'
```
By default it does a sameness equality (i.e. `===`) **with** case sensitivity
for determining if a value is valid. You can supply an optional second argument
function to define how values should be compared. The comparer function takes
the field value as the first argument and each valid value as the second
argument. You could use this to make values case insensitive. Returning a truthy
value in a comparison means that the field value is valid.
```js
const validator = isOneOf(
['foo', 'bar'],
(value, validValue) => (
value && value.toLowerCase() === validValue.toLowerCase()
)
);
validator('My Field')('FOO'); // undefined, so valid
```
### `matchesField`
`matchesField` checks that a field matches another field's value. This is
perfect for password confirmation fields.
`matchesField` takes the name of the other field as the first argument and an
optional second argument for the other field's label. The returned functions are
like the other validation functions.
```js
// Example 1
// =========
matchesField(
'password', // other field name
'Password' // other field label - optional
)('Password Confirmation')('yes', { password: 'no' });
// ▲ ▲ ▲
// | | |
// | | |
// this field name this field value other field value
// returns 'Password Confirmation does not match Password'
// ---------------------------------------------------------------------------
// Example 2
// =========
matchesField('password')('Password Confirmation')('yes', { password: 'yes' });
// undefined, so valid
```
With `combineValidators`:
```js
// ES2015
import {
combineValidators,
isRequired,
matchesField,
} from 'revalidate';
// Or ES5
var r = require('revalidate');
var combineValidators = r.combineValidators;
var isRequired = r.isRequired;
var matchesField = r.matchesField;
// Usage
const validate = combineValidators({
password: isRequired('Password'),
confirmPassword: matchesField('password')({
message: 'Passwords do not match',
}),
});
validate({
password: 'helloworld',
confirmPassword: 'helloworld',
}); // {}, so valid
validate({
password: 'helloworld',
confirmPassword: 'holamundo',
}); // { confirmPassword: 'Passwords do not match' }
```
### `isRequiredIf`
`isRequiredIf` allows you to conditionally require a value based on the result
of a predicate function. As long as your predicate function returns a truthy
value, the field value will be required.
This is perfect if you want to require a field if another field value is
present:
```js
const validator = combineValidators({
username: isRequiredIf(
values => values && !values.useEmailAsUsername
)('Username'),
});
validator(); // { username: 'Username is required' }
validator({
useEmailAsUsername: false,
}); // { username: 'Username is required' }
validator({
username: 'jfairbank',
useEmailAsUsername: false,
}); // {}
validator({
useEmailAsUsername: true,
}); // {}, so valid
```
If you compose `isRequiredIf` with `composeValidators`, your other validations
will still run even if your field isn't required:
```js
const validator = combineValidators({
username: composeValidators(
isRequiredIf(values => values && !values.useEmailAsUsername),
isAlphabetic
)('Username'),
});
// Field is required
validator({
username: '123',
useEmailAsUsername: false,
}); // { username: 'Username must be alphabetic' }
// Field is not required
validator({
username: '123',
useEmailAsUsername: true,
}); // { username: 'Username must be alphabetic' }
```
### `matchesPattern`
`matchesPattern` is a general purpose validator for validating values against
arbitrary regex patterns.
```js
const isAlphabetic = matchesPattern(/^[A-Za-z]+$/)('Username');
isAlphabetic('abc'); // undefined, so valid
isAlphabetic('123'); // 'Username must match pattern /^[A-Za-z]+$/'
```
**Note:** `matchesPattern` does not require a value, so falsy values will pass.
```js
isAlphabetic(); // undefined because not required, so valid
isAlphabetic(null); // undefined because not required, so valid
isAlphabetic(''); // undefined because not required, so valid
```
## Test Helpers
Revalidate includes some test helpers to make testing your validation functions
easier. You can import the helpers from `revalidate/assertions`. All helpers
return booleans.
- [`hasError`](#haserror)
- [`hasErrorAt`](#haserrorat)
- [`hasErrorOnlyAt`](#haserroronlyat)
### `hasError`
Use `hasError` to assert that a validation result has at least one error. Negate
to assert there are no errors. The only argument is the validation result from
your validate function.
```js
// ES2015
import { hasError } from 'revalidate/assertions';
// ES5
var hasError = require('revalidate/assertions').hasError;
// Single validators
// =================
const validateName = isRequired('Name');
hasError(validateName('')); // true
hasError(validateName('Tucker')); // false
// Composed validators
// ===================
const validateAge = composeValidators(
isRequired,
isNumeric
)('Age');
hasError(validateAge('')); // true
hasError(validateAge('abc')); // true
hasError(validateAge('10')); // false
// Composed validators with multiple errors
// ========================================
const validateAge = composeValidators(
isRequired,
isNumeric,
hasLengthLessThan(3)
)('Age');
hasError(validateAge('')); // true
hasError(validateAge('abc')); // true
hasError(validateAge('100')); // true
hasError(validateAge('one hundred')); // true
hasError(validateAge('10')); // false
// Combined validators
// ===================
const validateDog = combineValidators({
'name:' isRequired('Name'),
'age:' composeValidators(
isRequired,
isNumeric
)('Age'),
'favorite.meme': isRequired('Favorite Meme'),
});
// Missing name, returns true
hasError(validateDog({
age: '10',
favorite: { meme: 'Doge' },
}));
// Error with age, returns true
hasError(validateDog({
name: 'Tucker',
age: 'abc',
favorite: { meme: 'Doge' },
}));
// Missing name and age, returns true
hasError(validateDog({
favorite: { meme: 'Doge' },
}));
// Missing nested field 'favorite.meme', returns true
hasError(validateDog({
name: 'Tucker',
age: '10',
}));
// All fields valid, returns false
hasError(validateDog({
name: 'Tucker',
age: '10',
favorite: { meme: 'Doge' },
}));
```
### `hasErrorAt`
Use `hasErrorAt` with combined validators to assert a specific field has an
error. It takes two arguments, the validation result and the field key to check.
(**Note:** `hasErrorAt` only works with validators created from
`combineValidators`.)
```js
// ES2015
import { hasErrorAt } from 'revalidate/assertions';
// ES5
var hasErrorAt = require('revalidate/assertions').hasErrorAt;
// Missing name
const result = validateDog({
age: '10',
favorite: { meme: 'Doge' },
});
hasErrorAt(result, 'name'); // true
hasErrorAt(result, 'age'); // false
hasErrorAt(result, 'favorite.meme'); // false
// Error with age
const result = validateDog({
name: 'Tucker',
age: 'abc',
favorite: { meme: 'Doge' },
});
hasErrorAt(result, 'name'); // false
hasErrorAt(result, 'age'); // true
hasErrorAt(result, 'favorite.meme'); // false
// Missing name and age
const result = validateDog({
favorite: { meme: 'Doge' },
});
hasErrorAt(result, 'name'); // true
hasErrorAt(result, 'age'); // true
hasErrorAt(result, 'favorite.meme'); // false
// Missing nested field 'favorite.meme'
const result = validateDog({
name: 'Tucker',
age: '10',
});
hasErrorAt(result, 'name'); // false
hasErrorAt(result, 'age'); // false
hasErrorAt(result, 'favorite.meme'); // true
// All fields valid
const result = validateDog({
name: 'Tucker',
age: '10',
favorite: { meme: 'Doge' },
});
hasErrorAt(result, 'name'); // false
hasErrorAt(result, 'age'); // false
hasErrorAt(result, 'favorite.meme'); // false
```
### `hasErrorOnlyAt`
Use `hasErrorOnlyAt` with combined validators to assert a specific field is the
**ONLY** error in the validation result. It takes two arguments, the validation
result and the field key to check. (**Note:** `hasErrorOnlyAt` only works with
validators created from `combineValidators`.)
```js
// ES2015
import { hasErrorOnlyAt } from 'revalidate/assertions';
// ES5
var hasErrorOnlyAt = require('revalidate/assertions').hasErrorOnlyAt;
// Missing name
const result = validateDog({
age: '10',
favorite: { meme: 'Doge' },
});
hasErrorOnlyAt(result, 'name'); // true
hasErrorOnlyAt(result, 'age'); // false
hasErrorOnlyAt(result, 'favorite.meme'); // false
// Error with age
const result = validateDog({
name: 'Tucker',
age: 'abc',
favorite: { meme: 'Doge' },
});
hasErrorOnlyAt(result, 'name'); // false
hasErrorOnlyAt(result, 'age'); // true
hasErrorOnlyAt(result, 'favorite.meme'); // false
// Missing name and age
// Notice here that all checks return false because
// there are 2 errors
const result = validateDog({
favorite: { meme: 'Doge' },
});
hasErrorOnlyAt(result, 'name'); // false
hasErrorOnlyAt(result, 'age'); // false
hasErrorOnlyAt(result, 'favorite.meme'); // false
// Missing nested field 'favorite.meme'
const result = validateDog({
name: 'Tucker',
age: '10',
});
hasErrorOnlyAt(result, 'name'); // false
hasErrorOnlyAt(result, 'age'); // false
hasErrorOnlyAt(result, 'favorite.meme'); // true
// All fields valid
const result = validateDog({
name: 'Tucker',
age: '10',
favorite: { meme: 'Doge' },
});
hasErrorOnlyAt(result, 'name'); // false
hasErrorOnlyAt(result, 'age'); // false
hasErrorOnlyAt(result, 'favorite.meme'); // false
```
Revalidate has a host of options along with helper functions for building
validations and some common validation functions right out of the box. To learn
more, check out the [docs](http://revalidate.jeremyfairbank.com).

@@ -0,1 +1,2 @@

// @flow
import get from 'lodash/get';

@@ -5,3 +6,3 @@ import cloneDeep from 'lodash/cloneDeep';

export function hasError(result) {
export function hasError(result: any): boolean {
if (result == null) {

@@ -22,3 +23,3 @@ return false;

export function hasErrorAt(result, key) {
export function hasErrorAt(result: any, key?: string): boolean {
if (result == null || typeof result !== 'object') {

@@ -28,6 +29,18 @@ return false;

if (key == null) {
throw new Error('Please provide a key to check for an error.');
}
return hasError(get(result, key));
}
export function hasErrorOnlyAt(result, key) {
export function hasErrorOnlyAt(result: any, key?: string): boolean {
if (result == null || typeof result !== 'object') {
return false;
}
if (key == null) {
throw new Error('Please provide a key to check for an error.');
}
const omitted = cloneDeep(result);

@@ -34,0 +47,0 @@

@@ -0,7 +1,11 @@

// @flow
import internalCombineValidators from './internal/internalCombineValidators';
import ensureNestedValidators from './internal/ensureNestedValidators';
export default function combineValidators(validators) {
const finalValidators = ensureNestedValidators(validators);
return internalCombineValidators(finalValidators, true);
export default function combineValidators(
validators: Object,
options: CombineValidatorsOptions,
): ConfiguredCombinedValidator {
const finalValidators = ensureNestedValidators(validators, options);
return internalCombineValidators(finalValidators, true, options);
}

@@ -0,1 +1,2 @@

// @flow
import omit from 'lodash/omit';

@@ -7,10 +8,13 @@ import assign from 'object-assign';

export default function composeValidators(...validators) {
return function configurableValidators(sharedConfig) {
let config;
export default function composeValidators(
firstValidator: Validator | Object,
...validators: Array<Validator>
): ComposedCurryableValidator {
return function configurableValidators(sharedConfig?: string | ComposeConfig) {
let config: ComposeConfig;
if (typeof sharedConfig === 'string') {
config = { field: sharedConfig };
config = ({ field: sharedConfig }: ComposeConfig);
} else {
config = assign({}, sharedConfig);
config = (assign({}, sharedConfig): ComposeConfig);
}

@@ -20,2 +24,3 @@

return markAsValueValidator(createValidatorWithMultipleErrors(
firstValidator,
validators.slice(0),

@@ -26,4 +31,11 @@ omit(config, 'multiple')

if (typeof firstValidator === 'object') {
throw new Error(
'Please only pass in functions when composing ' +
'validators to produce a single error message.'
);
}
return markAsValueValidator(createValidatorWithSingleError(
validators.slice(0),
[firstValidator].concat(validators),
config

@@ -30,0 +42,0 @@ ));

@@ -1,43 +0,51 @@

import invariant from 'invariant';
import isPlainObject from 'lodash/isPlainObject';
// @flow
import markAsValueValidator from './internal/markAsValueValidator';
export default function createValidator(curriedDefinition, defaultMessageCreator) {
const messageCreatorIsString = typeof defaultMessageCreator === 'string';
function getMessage(
config: ?string | ?Config,
defaultMessageCreator: MessageCreator,
): string {
if (typeof config === 'object' && config != null) {
if (typeof config.message === 'string') {
return config.message;
}
invariant(
messageCreatorIsString || typeof defaultMessageCreator === 'function',
'Please provide a message string or message creator function'
);
if (typeof defaultMessageCreator === 'string') {
return defaultMessageCreator;
}
return function validator(config, value, allValues) {
const configIsObject = isPlainObject(config);
if (typeof config.field === 'string') {
return defaultMessageCreator(config.field);
}
}
if (!messageCreatorIsString) {
invariant(
typeof config === 'string' || configIsObject,
'Please provide a string or configuration object with a `field` or ' +
'`message` property'
);
if (typeof defaultMessageCreator === 'string') {
return defaultMessageCreator;
}
if (configIsObject) {
invariant(
'field' in config || 'message' in config,
'Please provide a `field` or `message` property'
);
}
}
if (typeof config === 'string') {
return defaultMessageCreator(config);
}
let message;
throw new Error(
'Please provide a string or configuration object with a `field` or ' +
'`message` property'
);
}
if (configIsObject && 'message' in config) {
message = config.message;
} else if (messageCreatorIsString) {
message = defaultMessageCreator;
} else if (configIsObject) {
message = defaultMessageCreator(config.field);
} else {
message = defaultMessageCreator(config);
}
export default function createValidator(
curriedDefinition: ValidatorImpl,
defaultMessageCreator?: MessageCreator,
): ConfigurableValidator {
if (
defaultMessageCreator == null ||
(typeof defaultMessageCreator !== 'string' && typeof defaultMessageCreator !== 'function')
) {
throw new Error('Please provide a message string or message creator function');
}
const finalMessageCreator = defaultMessageCreator;
return function validator(config, value, allValues) {
const message = getMessage(config, finalMessageCreator);
const valueValidator = curriedDefinition(message);

@@ -44,0 +52,0 @@

@@ -0,49 +1,59 @@

// @flow
import isValueValidator from './isValueValidator';
function buildErrorsArray(validators, validate) {
return validators.reduce((errors, validator) => {
const errorMessage = validate(validator);
function validateWithValidator(
value: ?any,
allValues: ?Object,
sharedConfig: Config,
validator: Validator,
): ?string {
if (isValueValidator(validator)) {
return validator(value, allValues);
}
if (errorMessage) {
errors.push(errorMessage);
}
return errors;
}, []);
return validator(sharedConfig, value, allValues);
}
function buildErrorsObject(validators, validate) {
return Object.keys(validators).reduce((errors, key) => {
const validator = validators[key];
const errorMessage = validate(validator);
export default function createValidatorWithMultipleErrors(
firstValidator: Validator | Object,
validators: Array<Validator>,
sharedConfig: Config,
): ConfiguredValidator {
if (typeof firstValidator === 'object') {
return function composedValidator(value, allValues): Object {
return Object.keys(firstValidator).reduce((errors, key) => {
const validator = firstValidator[key];
if (errorMessage) {
errors[key] = errorMessage;
}
const errorMessage = validateWithValidator(
value,
allValues,
sharedConfig,
validator,
);
return errors;
}, {});
}
if (errorMessage) {
errors[key] = errorMessage;
}
export default function createValidatorWithMultipleErrors(validators, sharedConfig) {
let buildErrors;
let finalValidators;
if (typeof validators[0] === 'object') {
buildErrors = buildErrorsObject;
finalValidators = validators[0];
} else {
buildErrors = buildErrorsArray;
finalValidators = validators;
return errors;
}, {});
};
}
return function composedValidator(value, allValues) {
return buildErrors(finalValidators, (validator) => {
if (isValueValidator(validator)) {
return validator(value, allValues);
return function composedValidator(value, allValues): Array<any> {
return [firstValidator].concat(validators).reduce((errors, validator) => {
const errorMessage = validateWithValidator(
value,
allValues,
sharedConfig,
validator,
);
if (errorMessage) {
errors.push(errorMessage);
}
return validator(sharedConfig, value, allValues);
});
return errors;
}, []);
};
}

@@ -0,4 +1,8 @@

// @flow
import isValueValidator from './isValueValidator';
export default function createValidatorWithSingleError(validators, sharedConfig) {
export default function createValidatorWithSingleError(
validators: Array<Validator>,
sharedConfig: ComposeConfig,
): ConfiguredValidator {
return function composedValidator(value, allValues) {

@@ -5,0 +9,0 @@ for (let i = 0, l = validators.length; i < l; i++) {

@@ -0,1 +1,2 @@

// @flow
import assign from 'object-assign';

@@ -5,3 +6,6 @@ import fillObjectFromPath from './fillObjectFromPath';

export default function ensureNestedValidators(validators) {
export default function ensureNestedValidators(
validators: Object,
options: CombineValidatorsOptions,
): Object {
const baseShape = Object.keys(validators).reduce(

@@ -16,3 +20,3 @@ (root, path) => assign(

return internalCombineNestedValidators(baseShape);
return internalCombineNestedValidators(baseShape, options);
}

@@ -0,4 +1,9 @@

// @flow
import assign from 'object-assign';
export default function fillObjectFromPath(object, path, finalValue) {
export default function fillObjectFromPath(
object: Object,
path: Array<string>,
finalValue: any,
): Object {
if (path.length <= 0) {

@@ -5,0 +10,0 @@ return finalValue;

@@ -0,8 +1,14 @@

// @flow
import internalCombineValidators from './internalCombineValidators';
export default function internalCombineNestedValidators(baseShape) {
export default function internalCombineNestedValidators(
baseShape: Object,
options: CombineValidatorsOptions,
): Object {
return Object.keys(baseShape).reduce((memo, key) => {
if (typeof baseShape[key] === 'object') {
memo[key] = internalCombineValidators(
internalCombineNestedValidators(baseShape[key])
internalCombineNestedValidators(baseShape[key], options),
false,
options,
);

@@ -9,0 +15,0 @@ } else {

@@ -0,10 +1,34 @@

// @flow
import parseFieldName from './parseFieldName';
export default function internalCombineValidators(validators, atRoot = false) {
return function valuesValidator(values = {}, allValues = {}) {
function defaultSerializeValues<T>(values: T): T {
return values;
}
export default function internalCombineValidators(
validators: Object,
atRoot: boolean,
options: CombineValidatorsOptions = {},
): ConfiguredCombinedValidator {
const serializeValues = atRoot && typeof options.serializeValues === 'function'
? options.serializeValues
: defaultSerializeValues;
function finalSerializeValues(values) {
if (values == null) {
return {};
}
return serializeValues(values) || {};
}
return function valuesValidator(values, allValues) {
const serializedValues = finalSerializeValues(values);
const serializedAllValues = finalSerializeValues(allValues);
return Object.keys(validators).reduce((errors, fieldName) => {
const parsedField = parseFieldName(fieldName);
const validator = validators[parsedField.fullName];
const value = values[parsedField.baseName];
const finalAllValues = atRoot ? values : allValues;
const value = serializedValues[parsedField.baseName];
const finalAllValues = atRoot ? serializedValues : serializedAllValues;

@@ -11,0 +35,0 @@ const errorMessage = parsedField.isArray

@@ -0,5 +1,6 @@

// @flow
import { VALUE_VALIDATOR_SYMBOL } from './symbols';
export default function isValueValidator(validator) {
export default function isValueValidator(validator: Validator): boolean {
return validator[VALUE_VALIDATOR_SYMBOL] === true;
}

@@ -0,6 +1,9 @@

// @flow
import { VALUE_VALIDATOR_SYMBOL } from './symbols';
export default function markAsValueValidator(valueValidator) {
export default function markAsValueValidator(
valueValidator: ConfiguredValidator,
): ConfiguredValidator {
valueValidator[VALUE_VALIDATOR_SYMBOL] = true;
return valueValidator;
}

@@ -1,2 +0,3 @@

export default function parseFieldName(fieldName) {
// @flow
export default function parseFieldName(fieldName: string): ParsedField {
const isArray = fieldName.indexOf('[]') > -1;

@@ -3,0 +4,0 @@ const baseName = isArray ? fieldName.replace('[]', '') : fieldName;

@@ -0,5 +1,6 @@

// @flow
const sym = typeof Symbol === 'function'
? Symbol
: id => `@@revalidate/${id}`;
: (id: string) => `@@revalidate/${id}`;
export default sym;

@@ -1,4 +0,4 @@

/* eslint-disable import/prefer-default-export */
// @flow
import sym from './sym';
export const VALUE_VALIDATOR_SYMBOL = sym('VALUE_VALIDATOR');

@@ -0,4 +1,8 @@

// @flow
import createValidator from '../../createValidator';
export default function internalMatchesPattern(regex, messageCreator) {
export default function internalMatchesPattern(
regex: RegExp,
messageCreator: MessageCreator,
): ConfigurableValidator {
return createValidator(

@@ -5,0 +9,0 @@ message => value => {

@@ -1,3 +0,4 @@

export default function valueMissing(value) {
// @flow
export default function valueMissing(value: any): boolean {
return value == null || (typeof value === 'string' && value.trim() === '');
}

@@ -0,4 +1,8 @@

// @flow
import createValidator from '../createValidator';
export default function hasLengthBetween(min, max) {
export default function hasLengthBetween(
min: number,
max: number,
): ConfigurableValidator {
return createValidator(

@@ -5,0 +9,0 @@ message => value => {

@@ -0,4 +1,7 @@

// @flow
import createValidator from '../createValidator';
export default function hasLengthGreaterThan(min) {
export default function hasLengthGreaterThan(
min: number,
): ConfigurableValidator {
return createValidator(

@@ -5,0 +8,0 @@ message => value => {

@@ -0,4 +1,7 @@

// @flow
import createValidator from '../createValidator';
export default function hasLengthLessThan(max) {
export default function hasLengthLessThan(
max: number,
): ConfigurableValidator {
return createValidator(

@@ -5,0 +8,0 @@ message => value => {

@@ -0,1 +1,2 @@

// @flow
import internalMatchesPattern from '../internal/validators/internalMatchesPattern';

@@ -2,0 +3,0 @@

@@ -0,1 +1,2 @@

// @flow
import internalMatchesPattern from '../internal/validators/internalMatchesPattern';

@@ -2,0 +3,0 @@

@@ -0,1 +1,2 @@

// @flow
import internalMatchesPattern from '../internal/validators/internalMatchesPattern';

@@ -2,0 +3,0 @@

@@ -0,11 +1,15 @@

// @flow
import findIndex from 'lodash/findIndex';
import createValidator from '../createValidator';
const defaultComparer = (value, optionValue) => value === optionValue;
const defaultComparer = (value: any, optionValue: any) => value === optionValue;
export default function isOneOf(values, comparer = defaultComparer) {
export default function isOneOf<T>(
values: Array<T>,
comparer: Comparer = defaultComparer,
): ConfigurableValidator {
const valuesClone = values.slice(0);
return createValidator(
message => value => {
message => (value: T) => {
if (value === undefined) {

@@ -12,0 +16,0 @@ return;

@@ -0,1 +1,2 @@

// @flow
import createValidator from '../createValidator';

@@ -2,0 +3,0 @@ import valueMissing from '../internal/valueMissing';

@@ -1,11 +0,8 @@

import invariant from 'invariant';
// @flow
import createValidator from '../createValidator';
import valueMissing from '../internal/valueMissing';
export default function isRequiredIf(condition) {
invariant(
typeof condition === 'function',
'Please provide a condition function to determine if a field should be required'
);
export default function isRequiredIf(
condition: (allValues: ?Object) => boolean,
): ConfigurableValidator {
return createValidator(

@@ -12,0 +9,0 @@ (message) => (value, allValues) => {

@@ -0,5 +1,9 @@

// @flow
import get from 'lodash/get';
import createValidator from '../createValidator';
export default function matchesField(otherField, otherFieldLabel) {
export default function matchesField(
otherField: string,
otherFieldLabel: string,
): ConfigurableValidator {
return createValidator(

@@ -6,0 +10,0 @@ message => (value, allValues) => {

@@ -0,8 +1,13 @@

// @flow
import internalMatchesPattern from '../internal/validators/internalMatchesPattern';
export default function matchesPattern(regex) {
export default function matchesPattern(
regex: RegExp,
): ConfigurableValidator {
const regexString = regex.toString();
return internalMatchesPattern(
regex,
field => `${field} must match pattern ${regex}`
field => `${field} must match pattern ${regexString}`
);
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc