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