joi 14.2.0 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +10 -0
- package/README.md +9 -118
- package/dist/joi-browser.min.js +1 -0
- package/lib/annotate.js +175 -0
- package/lib/base.js +1068 -0
- package/lib/cache.js +143 -0
- package/lib/common.js +216 -0
- package/lib/compile.js +283 -0
- package/lib/errors.js +160 -269
- package/lib/extend.js +312 -0
- package/lib/index.d.ts +2200 -0
- package/lib/index.js +179 -347
- package/lib/manifest.js +476 -0
- package/lib/messages.js +178 -0
- package/lib/modify.js +267 -0
- package/lib/ref.js +386 -25
- package/lib/schemas.js +291 -15
- package/lib/state.js +152 -0
- package/lib/template.js +427 -0
- package/lib/trace.js +346 -0
- package/lib/types/alternatives.js +329 -0
- package/lib/types/any.js +174 -0
- package/lib/types/array.js +775 -0
- package/lib/types/binary.js +98 -0
- package/lib/types/boolean.js +150 -0
- package/lib/types/date.js +233 -0
- package/lib/types/function.js +93 -0
- package/lib/types/keys.js +1043 -0
- package/lib/types/link.js +168 -0
- package/lib/types/number.js +335 -0
- package/lib/types/object.js +22 -0
- package/lib/types/string.js +820 -0
- package/lib/types/symbol.js +102 -0
- package/lib/validator.js +650 -0
- package/lib/values.js +263 -0
- package/package.json +34 -29
- package/CHANGELOG.md +0 -3
- package/LICENSE +0 -25
- package/lib/cast.js +0 -64
- package/lib/language.js +0 -166
- package/lib/set.js +0 -191
- package/lib/types/alternatives/index.js +0 -218
- package/lib/types/any/index.js +0 -978
- package/lib/types/any/settings.js +0 -36
- package/lib/types/array/index.js +0 -707
- package/lib/types/binary/index.js +0 -100
- package/lib/types/boolean/index.js +0 -100
- package/lib/types/date/index.js +0 -182
- package/lib/types/func/index.js +0 -90
- package/lib/types/lazy/index.js +0 -82
- package/lib/types/number/index.js +0 -248
- package/lib/types/object/index.js +0 -957
- package/lib/types/state.js +0 -11
- package/lib/types/string/index.js +0 -703
- package/lib/types/string/ip.js +0 -54
- package/lib/types/string/rfc3986.js +0 -219
- package/lib/types/string/uri.js +0 -46
- package/lib/types/symbol/index.js +0 -93
- package/lib/types/symbols.js +0 -5
package/lib/errors.js
CHANGED
|
@@ -1,74 +1,56 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const Annotate = require('./annotate');
|
|
4
|
+
const Common = require('./common');
|
|
5
|
+
const Template = require('./template');
|
|
4
6
|
|
|
5
|
-
const Hoek = require('hoek');
|
|
6
|
-
const Language = require('./language');
|
|
7
7
|
|
|
8
|
+
const internals = {};
|
|
8
9
|
|
|
9
|
-
// Declare internals
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
annotations: Symbol('joi-annotations')
|
|
13
|
-
};
|
|
11
|
+
exports.Report = class {
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
constructor(code, value, local, flags, messages, state, prefs) {
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.flags = flags;
|
|
17
|
+
this.messages = messages;
|
|
18
|
+
this.path = state.path;
|
|
19
|
+
this.prefs = prefs;
|
|
20
|
+
this.state = state;
|
|
21
|
+
this.value = value;
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
23
|
+
this.message = null;
|
|
24
|
+
this.template = null;
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
+
this.local = local || {};
|
|
27
|
+
this.local.label = exports.label(this.flags, this.state, this.prefs, this.messages);
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
29
|
+
if (this.value !== undefined &&
|
|
30
|
+
!this.local.hasOwnProperty('value')) {
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
let partial = '';
|
|
32
|
+
this.local.value = this.value;
|
|
33
|
+
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if (this.path.length) {
|
|
36
|
+
const key = this.path[this.path.length - 1];
|
|
37
|
+
if (typeof key !== 'object') {
|
|
38
|
+
this.local.key = key;
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
return wrapArrays ? '[' + partial + ']' : partial;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
return value.toString();
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
exports.Err = class {
|
|
43
|
+
_setTemplate(template) {
|
|
49
44
|
|
|
50
|
-
constructor(type, context, state, options, flags, message, template) {
|
|
51
|
-
|
|
52
|
-
this.isJoi = true;
|
|
53
|
-
this.type = type;
|
|
54
|
-
this.context = context || {};
|
|
55
|
-
this.context.key = state.path[state.path.length - 1];
|
|
56
|
-
this.context.label = state.key;
|
|
57
|
-
this.path = state.path;
|
|
58
|
-
this.options = options;
|
|
59
|
-
this.flags = flags;
|
|
60
|
-
this.message = message;
|
|
61
45
|
this.template = template;
|
|
62
46
|
|
|
63
|
-
|
|
47
|
+
if (!this.flags.label &&
|
|
48
|
+
this.path.length === 0) {
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
(this.context.label === '' ||
|
|
70
|
-
this.context.label === null)) {
|
|
71
|
-
this.context.label = localized.root || Language.errors.root;
|
|
50
|
+
const localized = this._template(this.template, 'root');
|
|
51
|
+
if (localized) {
|
|
52
|
+
this.local.label = localized;
|
|
53
|
+
}
|
|
72
54
|
}
|
|
73
55
|
}
|
|
74
56
|
|
|
@@ -78,294 +60,203 @@ exports.Err = class {
|
|
|
78
60
|
return this.message;
|
|
79
61
|
}
|
|
80
62
|
|
|
81
|
-
|
|
63
|
+
const code = this.code;
|
|
82
64
|
|
|
83
|
-
if (this.
|
|
84
|
-
|
|
65
|
+
if (!this.prefs.errors.render) {
|
|
66
|
+
return this.code;
|
|
85
67
|
}
|
|
86
68
|
|
|
87
|
-
const
|
|
69
|
+
const template = this._template(this.template) ||
|
|
70
|
+
this._template(this.prefs.messages) ||
|
|
71
|
+
this._template(this.messages);
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (format === undefined) {
|
|
92
|
-
return `Error code "${this.type}" is not defined, your custom type is missing the correct language definition`;
|
|
73
|
+
if (template === undefined) {
|
|
74
|
+
return `Error code "${code}" is not defined, your custom type is missing the correct messages definition`;
|
|
93
75
|
}
|
|
94
76
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
77
|
+
// Render and cache result
|
|
78
|
+
|
|
79
|
+
this.message = template.render(this.value, this.state, this.prefs, this.local, { errors: this.prefs.errors, messages: [this.prefs.messages, this.messages] });
|
|
80
|
+
if (!this.prefs.errors.label) {
|
|
81
|
+
this.message = this.message.replace(/^"" /, '').trim();
|
|
98
82
|
}
|
|
99
83
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (wrapArrays) {
|
|
103
|
-
return childrenString.slice(1, -1);
|
|
104
|
-
}
|
|
84
|
+
return this.message;
|
|
85
|
+
}
|
|
105
86
|
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
_template(messages, code) {
|
|
88
|
+
|
|
89
|
+
return exports.template(this.value, messages, code || this.code, this.state, this.prefs);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
108
92
|
|
|
109
|
-
const hasKey = /{{!?label}}/.test(format);
|
|
110
|
-
const skipKey = format.length > 2 && format[0] === '!' && format[1] === '!';
|
|
111
93
|
|
|
112
|
-
|
|
113
|
-
|
|
94
|
+
exports.path = function (path) {
|
|
95
|
+
|
|
96
|
+
let label = '';
|
|
97
|
+
for (const segment of path) {
|
|
98
|
+
if (typeof segment === 'object') { // Exclude array single path segment
|
|
99
|
+
continue;
|
|
114
100
|
}
|
|
115
101
|
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
format = localizedKey + format;
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
format = Hoek.reach(Language.errors, 'key') + format;
|
|
102
|
+
if (typeof segment === 'string') {
|
|
103
|
+
if (label) {
|
|
104
|
+
label += '.';
|
|
123
105
|
}
|
|
106
|
+
|
|
107
|
+
label += segment;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
label += `[${segment}]`;
|
|
124
111
|
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return label;
|
|
115
|
+
};
|
|
125
116
|
|
|
126
|
-
const message = format.replace(/{{(!?)([^}]+)}}/g, ($0, isSecure, name) => {
|
|
127
117
|
|
|
128
|
-
|
|
129
|
-
const normalized = internals.stringify(value, wrapArrays);
|
|
130
|
-
return (isSecure && this.options.escapeHtml ? Hoek.escapeHtml(normalized) : normalized);
|
|
131
|
-
});
|
|
118
|
+
exports.template = function (value, messages, code, state, prefs) {
|
|
132
119
|
|
|
133
|
-
|
|
120
|
+
if (!messages) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
134
123
|
|
|
135
|
-
|
|
124
|
+
if (Template.isTemplate(messages)) {
|
|
125
|
+
return code !== 'root' ? messages : null;
|
|
136
126
|
}
|
|
137
127
|
|
|
138
|
-
|
|
128
|
+
let lang = prefs.errors.language;
|
|
129
|
+
if (Common.isResolvable(lang)) {
|
|
130
|
+
lang = lang.resolve(value, state, prefs);
|
|
131
|
+
}
|
|
139
132
|
|
|
133
|
+
if (lang &&
|
|
134
|
+
messages[lang] &&
|
|
135
|
+
messages[lang][code] !== undefined) {
|
|
140
136
|
|
|
141
|
-
|
|
137
|
+
return messages[lang][code];
|
|
138
|
+
}
|
|
142
139
|
|
|
143
|
-
return
|
|
140
|
+
return messages[code];
|
|
144
141
|
};
|
|
145
142
|
|
|
146
143
|
|
|
147
|
-
exports.
|
|
144
|
+
exports.label = function (flags, state, prefs, messages) {
|
|
148
145
|
|
|
149
|
-
if (
|
|
150
|
-
return
|
|
146
|
+
if (flags.label) {
|
|
147
|
+
return flags.label;
|
|
151
148
|
}
|
|
152
149
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const details = [];
|
|
150
|
+
if (!prefs.errors.label) {
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
157
153
|
|
|
158
|
-
|
|
154
|
+
let path = state.path;
|
|
155
|
+
if (prefs.errors.label === 'key' &&
|
|
156
|
+
state.path.length > 1) {
|
|
159
157
|
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
path = state.path.slice(-1);
|
|
159
|
+
}
|
|
162
160
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
const normalized = exports.path(path);
|
|
162
|
+
if (normalized) {
|
|
163
|
+
return normalized;
|
|
164
|
+
}
|
|
166
165
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
166
|
+
return exports.template(null, prefs.messages, 'root', state, prefs) ||
|
|
167
|
+
messages && exports.template(null, messages, 'root', state, prefs) ||
|
|
168
|
+
'value';
|
|
169
|
+
};
|
|
172
170
|
|
|
173
|
-
let itemMessage;
|
|
174
|
-
if (parent === undefined) {
|
|
175
|
-
itemMessage = item.toString();
|
|
176
|
-
message = message + (message ? '. ' : '') + itemMessage;
|
|
177
|
-
}
|
|
178
171
|
|
|
179
|
-
|
|
172
|
+
exports.process = function (errors, original, prefs) {
|
|
180
173
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return override;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
details.push({
|
|
189
|
-
message: overrideMessage || itemMessage || item.toString(),
|
|
190
|
-
path: item.path,
|
|
191
|
-
type: item.type,
|
|
192
|
-
context: item.context
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
};
|
|
174
|
+
if (!errors) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
197
177
|
|
|
198
|
-
const override =
|
|
178
|
+
const { override, message, details } = exports.details(errors);
|
|
199
179
|
if (override) {
|
|
200
180
|
return override;
|
|
201
181
|
}
|
|
202
182
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
error.details = details;
|
|
207
|
-
error._object = object;
|
|
208
|
-
error.annotate = internals.annotate;
|
|
209
|
-
return error;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// Inspired by json-stringify-safe
|
|
214
|
-
internals.safeStringify = function (obj, spaces) {
|
|
183
|
+
if (prefs.errors.stack) {
|
|
184
|
+
return new exports.ValidationError(message, details, original);
|
|
185
|
+
}
|
|
215
186
|
|
|
216
|
-
|
|
187
|
+
const limit = Error.stackTraceLimit;
|
|
188
|
+
Error.stackTraceLimit = 0;
|
|
189
|
+
const validationError = new exports.ValidationError(message, details, original);
|
|
190
|
+
Error.stackTraceLimit = limit;
|
|
191
|
+
return validationError;
|
|
217
192
|
};
|
|
218
193
|
|
|
219
|
-
internals.serializer = function () {
|
|
220
|
-
|
|
221
|
-
const keys = [];
|
|
222
|
-
const stack = [];
|
|
223
194
|
|
|
224
|
-
|
|
195
|
+
exports.details = function (errors, options = {}) {
|
|
225
196
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
197
|
+
let messages = [];
|
|
198
|
+
const details = [];
|
|
229
199
|
|
|
230
|
-
|
|
231
|
-
};
|
|
200
|
+
for (const item of errors) {
|
|
232
201
|
|
|
233
|
-
|
|
202
|
+
// Override
|
|
234
203
|
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
stack.length = thisPos + 1;
|
|
239
|
-
keys.length = thisPos + 1;
|
|
240
|
-
keys[thisPos] = key;
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
stack.push(this);
|
|
244
|
-
keys.push(key);
|
|
204
|
+
if (item instanceof Error) {
|
|
205
|
+
if (options.override !== false) {
|
|
206
|
+
return { override: item };
|
|
245
207
|
}
|
|
246
208
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
stack.push(value);
|
|
253
|
-
}
|
|
209
|
+
const message = item.toString();
|
|
210
|
+
messages.push(message);
|
|
254
211
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
for (let i = 0; i < value.length; ++i) {
|
|
262
|
-
if (annotations.errors[i]) {
|
|
263
|
-
annotated.push(`_$idx$_${annotations.errors[i].sort().join(', ')}_$end$_`);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
annotated.push(value[i]);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
value = annotated;
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
const errorKeys = Object.keys(annotations.errors);
|
|
273
|
-
for (let i = 0; i < errorKeys.length; ++i) {
|
|
274
|
-
const errorKey = errorKeys[i];
|
|
275
|
-
value[`${errorKey}_$key$_${annotations.errors[errorKey].sort().join(', ')}_$end$_`] = value[errorKey];
|
|
276
|
-
value[errorKey] = undefined;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const missingKeys = Object.keys(annotations.missing);
|
|
280
|
-
for (let i = 0; i < missingKeys.length; ++i) {
|
|
281
|
-
const missingKey = missingKeys[i];
|
|
282
|
-
value[`_$miss$_${missingKey}|${annotations.missing[missingKey]}_$end$_`] = '__missing__';
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return value;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
212
|
+
details.push({
|
|
213
|
+
message,
|
|
214
|
+
type: 'override',
|
|
215
|
+
context: { error: item }
|
|
216
|
+
});
|
|
289
217
|
|
|
290
|
-
|
|
291
|
-
typeof value === 'function' || typeof value === 'symbol') {
|
|
292
|
-
return '[' + value.toString() + ']';
|
|
218
|
+
continue;
|
|
293
219
|
}
|
|
294
220
|
|
|
295
|
-
|
|
296
|
-
};
|
|
297
|
-
};
|
|
221
|
+
// Report
|
|
298
222
|
|
|
223
|
+
const message = item.toString();
|
|
224
|
+
messages.push(message);
|
|
299
225
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
226
|
+
details.push({
|
|
227
|
+
message,
|
|
228
|
+
path: item.path.filter((v) => typeof v !== 'object'),
|
|
229
|
+
type: item.code,
|
|
230
|
+
context: item.local
|
|
231
|
+
});
|
|
232
|
+
}
|
|
305
233
|
|
|
306
|
-
if (
|
|
307
|
-
|
|
234
|
+
if (messages.length > 1) {
|
|
235
|
+
messages = [...new Set(messages)];
|
|
308
236
|
}
|
|
309
237
|
|
|
310
|
-
|
|
238
|
+
return { message: messages.join('. '), details };
|
|
239
|
+
};
|
|
311
240
|
|
|
312
|
-
for (let i = this.details.length - 1; i >= 0; --i) { // Reverse order to process deepest child first
|
|
313
|
-
const pos = i + 1;
|
|
314
|
-
const error = this.details[i];
|
|
315
|
-
const path = error.path;
|
|
316
|
-
let ref = obj;
|
|
317
|
-
for (let j = 0; ; ++j) {
|
|
318
|
-
const seg = path[j];
|
|
319
241
|
|
|
320
|
-
|
|
321
|
-
ref = ref.clone(); // joi schemas are not cloned by hoek, we have to take this extra step
|
|
322
|
-
}
|
|
242
|
+
exports.ValidationError = class extends Error {
|
|
323
243
|
|
|
324
|
-
|
|
325
|
-
ref[seg] &&
|
|
326
|
-
typeof ref[seg] !== 'string') {
|
|
244
|
+
constructor(message, details, original) {
|
|
327
245
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const refAnnotations = ref[internals.annotations] = ref[internals.annotations] || { errors: {}, missing: {} };
|
|
332
|
-
const value = ref[seg];
|
|
333
|
-
const cacheKey = seg || error.context.label;
|
|
334
|
-
|
|
335
|
-
if (value !== undefined) {
|
|
336
|
-
refAnnotations.errors[cacheKey] = refAnnotations.errors[cacheKey] || [];
|
|
337
|
-
refAnnotations.errors[cacheKey].push(pos);
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
refAnnotations.missing[cacheKey] = pos;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
246
|
+
super(message);
|
|
247
|
+
this._original = original;
|
|
248
|
+
this.details = details;
|
|
346
249
|
}
|
|
347
250
|
|
|
348
|
-
|
|
349
|
-
key: /_\$key\$_([, \d]+)_\$end\$_"/g,
|
|
350
|
-
missing: /"_\$miss\$_([^|]+)\|(\d+)_\$end\$_": "__missing__"/g,
|
|
351
|
-
arrayIndex: /\s*"_\$idx\$_([, \d]+)_\$end\$_",?\n(.*)/g,
|
|
352
|
-
specials: /"\[(NaN|Symbol.*|-?Infinity|function.*|\(.*)]"/g
|
|
353
|
-
};
|
|
251
|
+
static isError(err) {
|
|
354
252
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
.replace(replacers.arrayIndex, ($0, $1, $2) => `\n${$2} ${redFgEscape}[${$1}]${endColor}`)
|
|
359
|
-
.replace(replacers.specials, ($0, $1) => $1);
|
|
253
|
+
return err instanceof exports.ValidationError;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
360
256
|
|
|
361
|
-
message = `${message}\n${redFgEscape}`;
|
|
362
257
|
|
|
363
|
-
|
|
364
|
-
const pos = i + 1;
|
|
365
|
-
message = `${message}\n[${pos}] ${this.details[i].message}`;
|
|
366
|
-
}
|
|
258
|
+
exports.ValidationError.prototype.isJoi = true;
|
|
367
259
|
|
|
368
|
-
|
|
260
|
+
exports.ValidationError.prototype.name = 'ValidationError';
|
|
369
261
|
|
|
370
|
-
|
|
371
|
-
};
|
|
262
|
+
exports.ValidationError.prototype.annotate = Annotate.error;
|