joi 13.2.0 → 13.3.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/README.md +1 -1
- package/lib/language.js +1 -0
- package/lib/set.js +105 -38
- package/lib/types/any/index.js +4 -4
- package/lib/types/object/index.js +28 -27
- package/lib/types/string/index.js +20 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Imagine you run facebook and you want visitors to sign up on the website with re
|
|
|
16
16
|
This is joi, joi allows you to create *blueprints* or *schemas* for JavaScript objects (an object that stores information) to ensure *validation* of key information.
|
|
17
17
|
|
|
18
18
|
# API
|
|
19
|
-
See the detailed [API Reference](https://github.com/hapijs/joi/blob/v13.
|
|
19
|
+
See the detailed [API Reference](https://github.com/hapijs/joi/blob/v13.3.0/API.md).
|
|
20
20
|
|
|
21
21
|
# Example
|
|
22
22
|
|
package/lib/language.js
CHANGED
|
@@ -143,6 +143,7 @@ exports.errors = {
|
|
|
143
143
|
isoDate: 'must be a valid ISO 8601 date',
|
|
144
144
|
guid: 'must be a valid GUID',
|
|
145
145
|
hex: 'must only contain hexadecimal characters',
|
|
146
|
+
hexAlign: 'hex decoded representation must be byte aligned',
|
|
146
147
|
base64: 'must be a valid base64 string',
|
|
147
148
|
hostname: 'must be a valid hostname',
|
|
148
149
|
normalize: 'must be unicode normalized in the {{form}} form',
|
package/lib/set.js
CHANGED
|
@@ -2,36 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
const Ref = require('./ref');
|
|
4
4
|
|
|
5
|
-
module.exports = class Set {
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
const internals = {};
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
internals.extendedCheckForValue = function (value, insensitive) {
|
|
10
|
+
|
|
11
|
+
const valueType = typeof value;
|
|
12
|
+
|
|
13
|
+
if (valueType === 'object') {
|
|
14
|
+
if (value instanceof Date) {
|
|
15
|
+
return (item) => {
|
|
16
|
+
|
|
17
|
+
return item instanceof Date && value.getTime() === item.getTime();
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (Buffer.isBuffer(value)) {
|
|
21
|
+
return (item) => {
|
|
22
|
+
|
|
23
|
+
return Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary');
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else if (insensitive && valueType === 'string') {
|
|
28
|
+
const lowercaseValue = value.toLowerCase();
|
|
29
|
+
return (item) => {
|
|
30
|
+
|
|
31
|
+
return typeof item === 'string' && lowercaseValue === item.toLowerCase();
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
module.exports = class InternalSet {
|
|
40
|
+
|
|
41
|
+
constructor(from) {
|
|
42
|
+
|
|
43
|
+
this._set = new Set(from);
|
|
44
|
+
this._hasRef = false;
|
|
10
45
|
}
|
|
11
46
|
|
|
12
47
|
add(value, refs) {
|
|
13
48
|
|
|
14
|
-
|
|
49
|
+
const isRef = Ref.isRef(value);
|
|
50
|
+
if (!isRef && this.has(value, null, null, false)) {
|
|
15
51
|
|
|
16
|
-
return;
|
|
52
|
+
return this;
|
|
17
53
|
}
|
|
18
54
|
|
|
19
55
|
if (refs !== undefined) { // If it's a merge, we don't have any refs
|
|
20
56
|
Ref.push(refs, value);
|
|
21
57
|
}
|
|
22
58
|
|
|
23
|
-
this._set.
|
|
59
|
+
this._set.add(value);
|
|
60
|
+
|
|
61
|
+
this._hasRef |= isRef;
|
|
62
|
+
|
|
24
63
|
return this;
|
|
25
64
|
}
|
|
26
65
|
|
|
27
66
|
merge(add, remove) {
|
|
28
67
|
|
|
29
|
-
for (
|
|
30
|
-
this.add(
|
|
68
|
+
for (const item of add._set) {
|
|
69
|
+
this.add(item);
|
|
31
70
|
}
|
|
32
71
|
|
|
33
|
-
for (
|
|
34
|
-
this.remove(
|
|
72
|
+
for (const item of remove._set) {
|
|
73
|
+
this.remove(item);
|
|
35
74
|
}
|
|
36
75
|
|
|
37
76
|
return this;
|
|
@@ -39,37 +78,68 @@ module.exports = class Set {
|
|
|
39
78
|
|
|
40
79
|
remove(value) {
|
|
41
80
|
|
|
42
|
-
this._set
|
|
81
|
+
this._set.delete(value);
|
|
43
82
|
return this;
|
|
44
83
|
}
|
|
45
84
|
|
|
46
85
|
has(value, state, options, insensitive) {
|
|
47
86
|
|
|
48
|
-
|
|
49
|
-
|
|
87
|
+
if (!this._set.size) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const hasValue = this._set.has(value);
|
|
92
|
+
if (hasValue) {
|
|
93
|
+
return hasValue;
|
|
94
|
+
}
|
|
50
95
|
|
|
51
|
-
|
|
52
|
-
|
|
96
|
+
const extendedCheck = internals.extendedCheckForValue(value, insensitive);
|
|
97
|
+
if (!extendedCheck) {
|
|
98
|
+
if (state && this._hasRef) {
|
|
99
|
+
for (let item of this._set) {
|
|
100
|
+
if (Ref.isRef(item)) {
|
|
101
|
+
item = item(state.reference || state.parent, options);
|
|
102
|
+
if (value === item || (Array.isArray(item) && item.includes(value))) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
53
107
|
}
|
|
54
108
|
|
|
55
|
-
|
|
56
|
-
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return this._has(value, state, options, extendedCheck);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_has(value, state, options, check) {
|
|
116
|
+
|
|
117
|
+
const checkRef = !!(state && this._hasRef);
|
|
118
|
+
|
|
119
|
+
const isReallyEqual = function (item) {
|
|
120
|
+
|
|
121
|
+
if (value === item) {
|
|
122
|
+
return true;
|
|
57
123
|
}
|
|
58
124
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (typeof value !== typeof item) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
125
|
+
return check(item);
|
|
126
|
+
};
|
|
64
127
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(Buffer.isBuffer(value) && Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary'))) {
|
|
128
|
+
for (let item of this._set) {
|
|
129
|
+
if (checkRef && Ref.isRef(item)) { // Only resolve references if there is a state, otherwise it's a merge
|
|
130
|
+
item = item(state.reference || state.parent, options);
|
|
69
131
|
|
|
70
|
-
|
|
132
|
+
if (Array.isArray(item)) {
|
|
133
|
+
if (item.find(isReallyEqual)) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
71
137
|
}
|
|
72
138
|
}
|
|
139
|
+
|
|
140
|
+
if (isReallyEqual(item)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
73
143
|
}
|
|
74
144
|
|
|
75
145
|
return false;
|
|
@@ -80,8 +150,7 @@ module.exports = class Set {
|
|
|
80
150
|
if (options && options.stripUndefined) {
|
|
81
151
|
const values = [];
|
|
82
152
|
|
|
83
|
-
for (
|
|
84
|
-
const item = this._set[i];
|
|
153
|
+
for (const item of this._set) {
|
|
85
154
|
if (item !== undefined) {
|
|
86
155
|
values.push(item);
|
|
87
156
|
}
|
|
@@ -90,22 +159,20 @@ module.exports = class Set {
|
|
|
90
159
|
return values;
|
|
91
160
|
}
|
|
92
161
|
|
|
93
|
-
return this._set
|
|
162
|
+
return Array.from(this._set);
|
|
94
163
|
}
|
|
95
164
|
|
|
96
165
|
slice() {
|
|
97
166
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return newSet;
|
|
167
|
+
const set = new InternalSet(this._set);
|
|
168
|
+
set._hasRef = this._hasRef;
|
|
169
|
+
return set;
|
|
102
170
|
}
|
|
103
171
|
|
|
104
172
|
concat(source) {
|
|
105
173
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return newSet;
|
|
174
|
+
const set = new InternalSet([...this._set, ...source._set]);
|
|
175
|
+
set._hasRef = !!(this._hasRef | source._hasRef);
|
|
176
|
+
return set;
|
|
110
177
|
}
|
|
111
178
|
};
|
package/lib/types/any/index.js
CHANGED
|
@@ -595,7 +595,7 @@ module.exports = internals.Any = class {
|
|
|
595
595
|
}
|
|
596
596
|
|
|
597
597
|
if (this._invalids.has(value, state, options, this._flags.insensitive)) {
|
|
598
|
-
errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid',
|
|
598
|
+
errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));
|
|
599
599
|
if (options.abortEarly ||
|
|
600
600
|
value === undefined) { // No reason to keep validating missing value
|
|
601
601
|
|
|
@@ -623,7 +623,7 @@ module.exports = internals.Any = class {
|
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
if (this._invalids.has(value, state, options, this._flags.insensitive)) {
|
|
626
|
-
errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid',
|
|
626
|
+
errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', { value, invalids: this._invalids.values({ stripUndefined: true }) }, state, options));
|
|
627
627
|
if (options.abortEarly) {
|
|
628
628
|
return finish();
|
|
629
629
|
}
|
|
@@ -634,13 +634,13 @@ module.exports = internals.Any = class {
|
|
|
634
634
|
// Required values did not match
|
|
635
635
|
|
|
636
636
|
if (this._flags.allowOnly) {
|
|
637
|
-
errors.push(this.createError('any.allowOnly', { valids: this._valids.values({ stripUndefined: true }) }, state, options));
|
|
637
|
+
errors.push(this.createError('any.allowOnly', { value, valids: this._valids.values({ stripUndefined: true }) }, state, options));
|
|
638
638
|
if (options.abortEarly) {
|
|
639
639
|
return finish();
|
|
640
640
|
}
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
-
//
|
|
643
|
+
// Validate tests
|
|
644
644
|
|
|
645
645
|
for (let i = 0; i < this._tests.length; ++i) {
|
|
646
646
|
const test = this._tests[i];
|
|
@@ -191,7 +191,7 @@ internals.Object = class extends Any {
|
|
|
191
191
|
return finish();
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
const unprocessed =
|
|
194
|
+
const unprocessed = new Set(Object.keys(target));
|
|
195
195
|
|
|
196
196
|
if (this._inner.children) {
|
|
197
197
|
const stripProps = [];
|
|
@@ -201,7 +201,7 @@ internals.Object = class extends Any {
|
|
|
201
201
|
const key = child.key;
|
|
202
202
|
const item = target[key];
|
|
203
203
|
|
|
204
|
-
delete
|
|
204
|
+
unprocessed.delete(key);
|
|
205
205
|
|
|
206
206
|
const localState = { key, path: state.path.concat(key), parent: target, reference: state.reference };
|
|
207
207
|
const result = child.schema._validate(item, localState, options);
|
|
@@ -230,24 +230,30 @@ internals.Object = class extends Any {
|
|
|
230
230
|
|
|
231
231
|
// Unknown keys
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
if (unprocessedKeys.length &&
|
|
235
|
-
this._inner.patterns.length) {
|
|
233
|
+
if (unprocessed.size && this._inner.patterns.length) {
|
|
236
234
|
|
|
237
|
-
for (
|
|
238
|
-
const
|
|
239
|
-
|
|
235
|
+
for (const key of unprocessed) {
|
|
236
|
+
const localState = {
|
|
237
|
+
key,
|
|
238
|
+
path: state.path.concat(key),
|
|
239
|
+
parent: target,
|
|
240
|
+
reference: state.reference
|
|
241
|
+
};
|
|
240
242
|
const item = target[key];
|
|
241
243
|
|
|
242
|
-
for (let
|
|
243
|
-
const pattern = this._inner.patterns[
|
|
244
|
+
for (let i = 0; i < this._inner.patterns.length; ++i) {
|
|
245
|
+
const pattern = this._inner.patterns[i];
|
|
244
246
|
|
|
245
247
|
if (pattern.regex.test(key)) {
|
|
246
|
-
delete
|
|
248
|
+
unprocessed.delete(key);
|
|
247
249
|
|
|
248
250
|
const result = pattern.rule._validate(item, localState, options);
|
|
249
251
|
if (result.errors) {
|
|
250
|
-
errors.push(this.createError('object.child', {
|
|
252
|
+
errors.push(this.createError('object.child', {
|
|
253
|
+
key,
|
|
254
|
+
child: pattern.rule._getLabel(key),
|
|
255
|
+
reason: result.errors
|
|
256
|
+
}, localState, options));
|
|
251
257
|
|
|
252
258
|
if (options.abortEarly) {
|
|
253
259
|
return finish();
|
|
@@ -258,11 +264,9 @@ internals.Object = class extends Any {
|
|
|
258
264
|
}
|
|
259
265
|
}
|
|
260
266
|
}
|
|
261
|
-
|
|
262
|
-
unprocessedKeys = Object.keys(unprocessed);
|
|
263
267
|
}
|
|
264
268
|
|
|
265
|
-
if ((this._inner.children || this._inner.patterns.length)
|
|
269
|
+
if (unprocessed.size && (this._inner.children || this._inner.patterns.length)) {
|
|
266
270
|
if ((options.stripUnknown && this._flags.allowUnknown !== true) ||
|
|
267
271
|
options.skipFunctions) {
|
|
268
272
|
|
|
@@ -271,27 +275,24 @@ internals.Object = class extends Any {
|
|
|
271
275
|
: false;
|
|
272
276
|
|
|
273
277
|
|
|
274
|
-
for (
|
|
275
|
-
const key = unprocessedKeys[i];
|
|
276
|
-
|
|
278
|
+
for (const key of unprocessed) {
|
|
277
279
|
if (stripUnknown) {
|
|
278
280
|
delete target[key];
|
|
279
|
-
delete
|
|
281
|
+
unprocessed.delete(key);
|
|
280
282
|
}
|
|
281
283
|
else if (typeof target[key] === 'function') {
|
|
282
|
-
delete
|
|
284
|
+
unprocessed.delete(key);
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
|
-
|
|
286
|
-
unprocessedKeys = Object.keys(unprocessed);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
if (
|
|
290
|
-
(this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {
|
|
289
|
+
if ((this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {
|
|
291
290
|
|
|
292
|
-
for (
|
|
293
|
-
|
|
294
|
-
|
|
291
|
+
for (const unprocessedKey of unprocessed) {
|
|
292
|
+
errors.push(this.createError('object.allowUnknown', { child: unprocessedKey }, {
|
|
293
|
+
key: unprocessedKey,
|
|
294
|
+
path: state.path.concat(unprocessedKey)
|
|
295
|
+
}, options, {}));
|
|
295
296
|
}
|
|
296
297
|
}
|
|
297
298
|
}
|
|
@@ -73,6 +73,10 @@ internals.String = class extends Any {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
if (this._flags.byteAligned && value.length % 2 !== 0) {
|
|
78
|
+
value = `0${value}`;
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
return {
|
|
@@ -399,18 +403,32 @@ internals.String = class extends Any {
|
|
|
399
403
|
});
|
|
400
404
|
}
|
|
401
405
|
|
|
402
|
-
hex() {
|
|
406
|
+
hex(hexOptions = {}) {
|
|
403
407
|
|
|
408
|
+
Hoek.assert(typeof hexOptions === 'object', 'hex options must be an object');
|
|
409
|
+
Hoek.assert(typeof hexOptions.byteAligned === 'undefined' || typeof hexOptions.byteAligned === 'boolean',
|
|
410
|
+
'byteAligned must be boolean');
|
|
411
|
+
|
|
412
|
+
const byteAligned = hexOptions.byteAligned === true;
|
|
404
413
|
const regex = /^[a-f0-9]+$/i;
|
|
405
414
|
|
|
406
|
-
|
|
415
|
+
const obj = this._test('hex', regex, function (value, state, options) {
|
|
407
416
|
|
|
408
417
|
if (regex.test(value)) {
|
|
418
|
+
if (byteAligned && value.length % 2 !== 0) {
|
|
419
|
+
return this.createError('string.hexAlign', { value }, state, options);
|
|
420
|
+
}
|
|
409
421
|
return value;
|
|
410
422
|
}
|
|
411
423
|
|
|
412
424
|
return this.createError('string.hex', { value }, state, options);
|
|
413
425
|
});
|
|
426
|
+
|
|
427
|
+
if (byteAligned) {
|
|
428
|
+
obj._flags.byteAligned = true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return obj;
|
|
414
432
|
}
|
|
415
433
|
|
|
416
434
|
base64(base64Options = {}) {
|