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