-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathvalidate.js
199 lines (182 loc) · 5.79 KB
/
validate.js
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import toPath from "lodash.topath";
import Ajv from "ajv";
const ajv = new Ajv({
errorDataPath: "property",
allErrors: true,
multipleOfPrecision: 8,
});
// add custom formats
ajv.addFormat(
"data-url",
/^data:([a-z]+\/[a-z0-9-+.]+)?;name=(.*);base64,(.*)$/
);
ajv.addFormat(
"color",
/^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/
);
import { isObject, mergeObjects } from "./utils";
function toErrorSchema(errors) {
// Transforms a ajv validation errors list:
// [
// {property: ".level1.level2[2].level3", message: "err a"},
// {property: ".level1.level2[2].level3", message: "err b"},
// {property: ".level1.level2[4].level3", message: "err b"},
// ]
// Into an error tree:
// {
// level1: {
// level2: {
// 2: {level3: {errors: ["err a", "err b"]}},
// 4: {level3: {errors: ["err b"]}},
// }
// }
// };
if (!errors.length) {
return {};
}
return errors.reduce((errorSchema, error) => {
const { property, message } = error;
const path = toPath(property);
let parent = errorSchema;
// If the property is at the root (.level1) then toPath creates
// an empty array element at the first index. Remove it.
if (path.length > 0 && path[0] === "") {
path.splice(0, 1);
}
for (const segment of path.slice(0)) {
if (!(segment in parent)) {
parent[segment] = {};
}
parent = parent[segment];
}
if (Array.isArray(parent.__errors)) {
// We store the list of errors for this node in a property named __errors
// to avoid name collision with a possible sub schema field named
// "errors" (see `validate.createErrorHandler`).
parent.__errors = parent.__errors.concat(message);
} else {
parent.__errors = [message];
}
return errorSchema;
}, {});
}
export function toErrorList(errorSchema, fieldName = "root") {
// XXX: We should transform fieldName as a full field path string.
let errorList = [];
if ("__errors" in errorSchema) {
errorList = errorList.concat(
errorSchema.__errors.map(stack => {
return {
stack: `${fieldName}: ${stack}`,
};
})
);
}
return Object.keys(errorSchema).reduce((acc, key) => {
if (key !== "__errors") {
acc = acc.concat(toErrorList(errorSchema[key], key));
}
return acc;
}, errorList);
}
function createErrorHandler(formData) {
const handler = {
// We store the list of errors for this node in a property named __errors
// to avoid name collision with a possible sub schema field named
// "errors" (see `utils.toErrorSchema`).
__errors: [],
addError(message) {
this.__errors.push(message);
},
};
if (isObject(formData)) {
return Object.keys(formData).reduce((acc, key) => {
return { ...acc, [key]: createErrorHandler(formData[key]) };
}, handler);
}
if (Array.isArray(formData)) {
return formData.reduce((acc, value, key) => {
return { ...acc, [key]: createErrorHandler(value) };
}, handler);
}
return handler;
}
function unwrapErrorHandler(errorHandler) {
return Object.keys(errorHandler).reduce((acc, key) => {
if (key === "addError") {
return acc;
} else if (key === "__errors") {
return { ...acc, [key]: errorHandler[key] };
}
return { ...acc, [key]: unwrapErrorHandler(errorHandler[key]) };
}, {});
}
/**
* Transforming the error output from ajv to format used by jsonschema.
* At some point, components should be updated to support ajv.
*/
function transformAjvErrors(errors = []) {
if (errors === null) {
return [];
}
return errors.map(e => {
const { dataPath, keyword, message, params } = e;
let property = `${dataPath}`;
// put data in expected format
return {
name: keyword,
property,
message,
params, // specific to ajv
stack: `${property} ${message}`.trim(),
};
});
}
/**
* This function processes the formData with a user `validate` contributed
* function, which receives the form data and an `errorHandler` object that
* will be used to add custom validation errors for each field.
*/
export default function validateFormData(
formData,
schema,
customValidate,
transformErrors
) {
try {
ajv.validate(schema, formData);
} catch (e) {
// swallow errors thrown in ajv due to invalid schemas, these
// still get displayed
}
let errors = transformAjvErrors(ajv.errors);
// Clear errors to prevent persistent errors, see #1104
ajv.errors = null;
if (typeof transformErrors === "function") {
errors = transformErrors(errors);
}
const errorSchema = toErrorSchema(errors);
if (typeof customValidate !== "function") {
return { errors, errorSchema };
}
const errorHandler = customValidate(formData, createErrorHandler(formData));
const userErrorSchema = unwrapErrorHandler(errorHandler);
const newErrorSchema = mergeObjects(errorSchema, userErrorSchema, true);
// XXX: The errors list produced is not fully compliant with the format
// exposed by the jsonschema lib, which contains full field paths and other
// properties.
const newErrors = toErrorList(newErrorSchema);
return { errors: newErrors, errorSchema: newErrorSchema };
}
/**
* Validates data against a schema, returning true if the data is valid, or
* false otherwise. If the schema is invalid, then this function will return
* false.
*/
export function isValid(schema, data) {
try {
return ajv.validate(schema, data);
} catch (e) {
return false;
}
}