on fa53035 help sign in
bridge/src/form/validators.js
153 lines 4.8 kB history raw
1import { some } from 'lodash';
3import {
4 validateNotEmpty,
5 validatePatq,
6 validateMnemonic,
7 validatePoint,
8 validateMaximumPatpByteLength,
9 validateOneOf,
10 validateHexString,
11 validateHexLength,
12 validateEthereumAddress,
13 validateGreaterThan,
14 validateEmail,
15 validateNotNullAddress,
16} from 'lib/validators';
17import isPromise from 'lib/isPromise';
19// iterate over validators, exiting early if there's an error
20const buildValidator = (validators = [], validate) => value => {
21 for (const validator of validators) {
22 try {
23 const error = validator(value);
24 if (error) {
25 return error;
26 }
27 } catch (error) {
28 console.error(error);
29 return error.message;
30 }
31 }
33 if (validate) {
34 // the final validate function can optionally return a promise
35 return validate(value);
36 }
37};
39// maps a validator across an array of values
40export const buildArrayValidator = validator => values => {
41 const errorsOrPromises = values.map(validator);
42 if (some(errorsOrPromises, isPromise)) {
43 return Promise.all(errorsOrPromises);
44 }
46 return errorsOrPromises;
47};
49// error object has errors if some of its fields are
50// 1) an array with any defined values
51// 2) any defined values
52export const hasErrors = iter =>
53 some(iter, v => (Array.isArray(v) ? hasErrors(v) : v !== undefined));
55export const buildPatqValidator = (validators = []) =>
56 buildValidator([validateNotEmpty, validatePatq, ...validators]);
57export const buildMnemonicValidator = () =>
58 buildValidator([validateNotEmpty, validateMnemonic]);
59export const buildCheckboxValidator = mustBe =>
60 buildValidator([
61 validateOneOf(mustBe !== undefined ? [mustBe] : [true, false]),
62 ]);
63export const buildPassphraseValidator = () => buildValidator([]);
64// TODO: validate hdpath format
65export const buildHdPathValidator = () => buildValidator([validateNotEmpty]);
66export const buildPointValidator = (size = 4, validators = []) =>
67 buildValidator([
68 validateNotEmpty,
69 validateMaximumPatpByteLength(size),
70 validatePoint,
71 ...validators,
72 ]);
73export const buildSelectValidator = options =>
74 buildValidator([validateOneOf(options.map(option => option.value))]);
75export const buildHexValidator = length =>
76 buildValidator([
77 validateNotEmpty,
78 validateHexString,
79 validateHexLength(length),
80 ]);
81export const buildUploadValidator = () => buildValidator([validateNotEmpty]);
82export const buildAddressValidator = () =>
83 buildValidator([
84 validateNotEmpty,
85 validateHexString,
86 validateHexLength(40),
87 validateNotNullAddress,
88 validateEthereumAddress,
89 ]);
90export const buildNumberValidator = (min = 0) =>
91 buildValidator([validateGreaterThan(min)]);
92export const buildEmailValidator = validate =>
93 buildValidator([validateNotEmpty, validateEmail], validate);
95// the form validator is the composition of all of the field validators
96// plus an additional form validator function
97export const composeValidator = (
98 fieldValidators = {},
99 // the default form validator just returns field-level validations
100 formValidator = (values, errors) => errors
101) => {
102 const names = Object.keys(fieldValidators);
104 const fieldLevelValidators = names.map(name => value =>
105 fieldValidators[name](value)
106 );
108 // async reduce errors per-field into an errors object
109 const fieldLevelValidator = values => {
110 const errorsOrPromises = names.map((name, i) =>
111 fieldLevelValidators[i](values[name])
112 );
114 const reduce = errors =>
115 names.reduce(
116 (memo, name, i) => ({
117 ...memo,
118 [name]: errors[i],
119 }),
120 {}
121 );
123 if (some(errorsOrPromises, isPromise)) {
124 // if any of these results are a promise, await them all then reduce
125 return Promise.all(errorsOrPromises).then(reduce);
126 }
128 // otherwise return immediately
129 return reduce(errorsOrPromises);
130 };
132 // the final-form `validate` function
133 // NOTE: if we return a Promise to final-form it will toggle the `validating`
134 // state, which is expected. If our promise resolves immediately, however,
135 // that means our `validating` state flickers the UI and it looks pretty bad.
136 // The solution is to conditionally return a promise only when necessary.
137 return values => {
138 // ask for field-level errors
139 const errorsOrPromise = fieldLevelValidator(values);
141 // pass the current values and their validity to the form-level validator
142 // that can implement conditional logic and more complex validations
143 const runFormValidator = errors => formValidator(values, errors);
145 if (isPromise(errorsOrPromise)) {
146 // if promise, promise
147 return errorsOrPromise.then(runFormValidator);
148 }
150 // otherwise, it's an errors object
151 return runFormValidator(errorsOrPromise);
152 };
153};