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