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