OptionsValidationError.js
6.34 KB
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
'use strict';
/* eslint no-param-reassign: 'off' */
const optionsSchema = require('./optionsSchema.json');
const indent = (str, prefix, firstLine) => {
if (firstLine) {
return prefix + str.replace(/\n(?!$)/g, `\n${prefix}`);
}
return str.replace(/\n(?!$)/g, `\n${prefix}`);
};
const getSchemaPart = (path, parents, additionalPath) => {
parents = parents || 0;
path = path.split('/');
path = path.slice(0, path.length - parents);
if (additionalPath) {
additionalPath = additionalPath.split('/');
path = path.concat(additionalPath);
}
let schemaPart = optionsSchema;
for (let i = 1; i < path.length; i++) {
const inner = schemaPart[path[i]];
if (inner) { schemaPart = inner; }
}
return schemaPart;
};
const getSchemaPartText = (schemaPart, additionalPath) => {
if (additionalPath) {
for (let i = 0; i < additionalPath.length; i++) {
const inner = schemaPart[additionalPath[i]];
if (inner) { schemaPart = inner; }
}
}
while (schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref);
let schemaText = OptionsValidationError.formatSchema(schemaPart); // eslint-disable-line
if (schemaPart.description) { schemaText += `\n${schemaPart.description}`; }
return schemaText;
};
class OptionsValidationError extends Error {
constructor(validationErrors) {
super();
if (Error.hasOwnProperty('captureStackTrace')) { // eslint-disable-line
Error.captureStackTrace(this, this.constructor);
}
this.name = 'WebpackDevServerOptionsValidationError';
this.message = `${'Invalid configuration object. ' +
'webpack-dev-server has been initialised using a configuration object that does not match the API schema.\n'}${
validationErrors.map(err => ` - ${indent(OptionsValidationError.formatValidationError(err), ' ', false)}`).join('\n')}`;
this.validationErrors = validationErrors;
}
static formatSchema(schema, prevSchemas) {
prevSchemas = prevSchemas || [];
const formatInnerSchema = (innerSchema, addSelf) => {
if (!addSelf) return OptionsValidationError.formatSchema(innerSchema, prevSchemas);
if (prevSchemas.indexOf(innerSchema) >= 0) return '(recursive)';
return OptionsValidationError.formatSchema(innerSchema, prevSchemas.concat(schema));
};
if (schema.type === 'string') {
if (schema.minLength === 1) { return 'non-empty string'; } else if (schema.minLength > 1) { return `string (min length ${schema.minLength})`; }
return 'string';
} else if (schema.type === 'boolean') {
return 'boolean';
} else if (schema.type === 'number') {
return 'number';
} else if (schema.type === 'object') {
if (schema.properties) {
const required = schema.required || [];
return `object { ${Object.keys(schema.properties).map((property) => {
if (required.indexOf(property) < 0) return `${property}?`;
return property;
}).concat(schema.additionalProperties ? ['...'] : []).join(', ')} }`;
}
if (schema.additionalProperties) {
return `object { <key>: ${formatInnerSchema(schema.additionalProperties)} }`;
}
return 'object';
} else if (schema.type === 'array') {
return `[${formatInnerSchema(schema.items)}]`;
}
switch (schema.instanceof) {
case 'Function':
return 'function';
case 'RegExp':
return 'RegExp';
default:
}
if (schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
if (schema.allOf) return schema.allOf.map(formatInnerSchema).join(' & ');
if (schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(' | ');
if (schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(' | ');
if (schema.enum) return schema.enum.map(item => JSON.stringify(item)).join(' | ');
return JSON.stringify(schema, 0, 2);
}
static formatValidationError(err) {
const dataPath = `configuration${err.dataPath}`;
if (err.keyword === 'additionalProperties') {
return `${dataPath} has an unknown property '${err.params.additionalProperty}'. These properties are valid:\n${getSchemaPartText(err.parentSchema)}`;
} else if (err.keyword === 'oneOf' || err.keyword === 'anyOf') {
if (err.children && err.children.length > 0) {
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}\n` +
`Details:\n${err.children.map(e => ` * ${indent(OptionsValidationError.formatValidationError(e), ' ', false)}`).join('\n')}`;
}
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
} else if (err.keyword === 'enum') {
if (err.parentSchema && err.parentSchema.enum && err.parentSchema.enum.length === 1) {
return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`;
}
return `${dataPath} should be one of these:\n${getSchemaPartText(err.parentSchema)}`;
} else if (err.keyword === 'allOf') {
return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`;
} else if (err.keyword === 'type') {
switch (err.params.type) {
case 'object':
return `${dataPath} should be an object.`;
case 'string':
return `${dataPath} should be a string.`;
case 'boolean':
return `${dataPath} should be a boolean.`;
case 'number':
return `${dataPath} should be a number.`;
case 'array':
return `${dataPath} should be an array:\n${getSchemaPartText(err.parentSchema)}`;
default:
}
return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText(err.parentSchema)}`;
} else if (err.keyword === 'instanceof') {
return `${dataPath} should be an instance of ${getSchemaPartText(err.parentSchema)}.`;
} else if (err.keyword === 'required') {
const missingProperty = err.params.missingProperty.replace(/^\./, '');
return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText(err.parentSchema, ['properties', missingProperty])}`;
} else if (err.keyword === 'minLength' || err.keyword === 'minItems') {
if (err.params.limit === 1) { return `${dataPath} should not be empty.`; }
return `${dataPath} ${err.message}`;
}
// eslint-disable-line no-fallthrough
return `${dataPath} ${err.message} (${JSON.stringify(err, 0, 2)}).\n${getSchemaPartText(err.parentSchema)}`;
}
}
module.exports = OptionsValidationError;