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