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