/
validate.ts
129 lines (113 loc) · 3.61 KB
/
validate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import defaultConfig from './defaultConfig';
import type {ValidationOptions} from './types';
import {ValidationError} from './utils';
let hasDeprecationWarnings = false;
const shouldSkipValidationForPath = (
path: Array<string>,
key: string,
denylist?: Array<string>,
) => (denylist ? denylist.includes([...path, key].join('.')) : false);
const _validate = (
config: Record<string, any>,
exampleConfig: Record<string, any>,
options: ValidationOptions,
path: Array<string> = [],
): {hasDeprecationWarnings: boolean} => {
if (
typeof config !== 'object' ||
config == null ||
typeof exampleConfig !== 'object' ||
exampleConfig == null
) {
return {hasDeprecationWarnings};
}
for (const key in config) {
if (
options.deprecatedConfig &&
key in options.deprecatedConfig &&
typeof options.deprecate === 'function'
) {
const isDeprecatedKey = options.deprecate(
config,
key,
options.deprecatedConfig,
options,
);
hasDeprecationWarnings = hasDeprecationWarnings || isDeprecatedKey;
} else if (allowsMultipleTypes(key)) {
const value = config[key];
if (
typeof options.condition === 'function' &&
typeof options.error === 'function'
) {
if (key === 'maxWorkers' && !isOfTypeStringOrNumber(value)) {
throw new ValidationError(
'Validation Error',
`${key} has to be of type string or number`,
`maxWorkers=50% or\nmaxWorkers=3`,
);
}
}
} else if (Object.hasOwnProperty.call(exampleConfig, key)) {
if (
typeof options.condition === 'function' &&
typeof options.error === 'function' &&
!options.condition(config[key], exampleConfig[key])
) {
options.error(key, config[key], exampleConfig[key], options, path);
}
} else if (
shouldSkipValidationForPath(path, key, options.recursiveDenylist)
) {
// skip validating unknown options inside blacklisted paths
} else {
options.unknown &&
options.unknown(config, exampleConfig, key, options, path);
}
if (
options.recursive &&
!Array.isArray(exampleConfig[key]) &&
options.recursiveDenylist &&
!shouldSkipValidationForPath(path, key, options.recursiveDenylist)
) {
_validate(config[key], exampleConfig[key], options, [...path, key]);
}
}
return {hasDeprecationWarnings};
};
const allowsMultipleTypes = (key: string): boolean => key === 'maxWorkers';
const isOfTypeStringOrNumber = (value: unknown): boolean =>
typeof value === 'number' || typeof value === 'string';
const validate = (
config: Record<string, unknown>,
options: ValidationOptions,
): {hasDeprecationWarnings: boolean; isValid: boolean} => {
hasDeprecationWarnings = false;
// Preserve default denylist entries even with user-supplied denylist
const combinedDenylist: Array<string> = [
...(defaultConfig.recursiveDenylist || []),
...(options.recursiveDenylist || []),
];
const defaultedOptions: ValidationOptions = Object.assign({
...defaultConfig,
...options,
recursiveDenylist: combinedDenylist,
title: options.title || defaultConfig.title,
});
const {hasDeprecationWarnings: hdw} = _validate(
config,
options.exampleConfig,
defaultedOptions,
);
return {
hasDeprecationWarnings: hdw,
isValid: true,
};
};
export default validate;