joi 13.1.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/LICENSE +1 -1
- package/README.md +8 -12
- package/lib/index.js +26 -18
- package/lib/language.js +3 -1
- package/lib/set.js +105 -38
- package/lib/types/any/index.js +15 -43
- package/lib/types/any/settings.js +35 -0
- package/lib/types/array/index.js +8 -4
- package/lib/types/binary/index.js +1 -1
- package/lib/types/number/index.js +12 -0
- package/lib/types/object/index.js +41 -27
- package/lib/types/string/index.js +20 -2
- package/package.json +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -3,20 +3,10 @@
|
|
|
3
3
|
Object schema description language and validator for JavaScript objects.
|
|
4
4
|
|
|
5
5
|
[](http://badge.fury.io/js/joi)
|
|
6
|
-
[](https://david-dm.org/hapijs/joi)
|
|
12
|
-
[](https://david-dm.org/hapijs/joi#info=devDependencies)
|
|
13
|
-
|
|
14
|
-
-->
|
|
6
|
+
[](https://travis-ci.org/hapijs/joi)
|
|
15
7
|
[](https://nodesecurity.io/orgs/hapijs/projects/0394bf83-b5bc-410b-878c-e8cf1b92033e)
|
|
16
8
|
[](https://snyk.io/test/github/hapijs/joi)
|
|
17
9
|
|
|
18
|
-
[](https://gitter.im/hapijs/joi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
19
|
-
|
|
20
10
|
Lead Maintainer: [Nicolas Morel](https://github.com/marsup)
|
|
21
11
|
|
|
22
12
|
# Introduction
|
|
@@ -26,7 +16,7 @@ Imagine you run facebook and you want visitors to sign up on the website with re
|
|
|
26
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.
|
|
27
17
|
|
|
28
18
|
# API
|
|
29
|
-
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).
|
|
30
20
|
|
|
31
21
|
# Example
|
|
32
22
|
|
|
@@ -129,3 +119,9 @@ When validating a schema:
|
|
|
129
119
|
# Browsers
|
|
130
120
|
|
|
131
121
|
Joi doesn't directly support browsers, but you could use [joi-browser](https://github.com/jeffbski/joi-browser) for an ES5 build of Joi that works in browsers, or as a source of inspiration for your own builds.
|
|
122
|
+
|
|
123
|
+
## Acknowledgements
|
|
124
|
+
|
|
125
|
+
This project is kindly sponsored by:
|
|
126
|
+
|
|
127
|
+
[](https://nearform.com)
|
package/lib/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const Cast = require('./cast');
|
|
|
8
8
|
const Errors = require('./errors');
|
|
9
9
|
const Lazy = require('./types/lazy');
|
|
10
10
|
const Ref = require('./ref');
|
|
11
|
+
const Settings = require('./types/any/settings');
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
// Declare internals
|
|
@@ -191,25 +192,31 @@ internals.root = function () {
|
|
|
191
192
|
root.reach = function (schema, path) {
|
|
192
193
|
|
|
193
194
|
Hoek.assert(schema && schema instanceof Any, 'you must provide a joi schema');
|
|
194
|
-
Hoek.assert(typeof path === 'string', 'path must be a string');
|
|
195
|
+
Hoek.assert(Array.isArray(path) || typeof path === 'string', 'path must be a string or an array of strings');
|
|
195
196
|
|
|
196
|
-
|
|
197
|
-
return schema;
|
|
198
|
-
}
|
|
197
|
+
const reach = (sourceSchema, schemaPath) => {
|
|
199
198
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
199
|
+
if (!schemaPath.length) {
|
|
200
|
+
return sourceSchema;
|
|
201
|
+
}
|
|
205
202
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (child.key === key) {
|
|
210
|
-
return this.reach(child.schema, path.substr(key.length + 1));
|
|
203
|
+
const children = sourceSchema._inner.children;
|
|
204
|
+
if (!children) {
|
|
205
|
+
return;
|
|
211
206
|
}
|
|
212
|
-
|
|
207
|
+
|
|
208
|
+
const key = schemaPath.shift();
|
|
209
|
+
for (let i = 0; i < children.length; ++i) {
|
|
210
|
+
const child = children[i];
|
|
211
|
+
if (child.key === key) {
|
|
212
|
+
return reach(child.schema, schemaPath);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const schemaPath = typeof path === 'string' ? path.split('.') : path.slice();
|
|
218
|
+
|
|
219
|
+
return reach(schema, schemaPath);
|
|
213
220
|
};
|
|
214
221
|
|
|
215
222
|
root.lazy = function (fn) {
|
|
@@ -276,9 +283,10 @@ internals.root = function () {
|
|
|
276
283
|
this._type = extension.name;
|
|
277
284
|
|
|
278
285
|
if (extension.language) {
|
|
279
|
-
this._settings = this._settings
|
|
280
|
-
|
|
281
|
-
|
|
286
|
+
this._settings = Settings.concat(this._settings, {
|
|
287
|
+
language: {
|
|
288
|
+
[extension.name]: extension.language
|
|
289
|
+
}
|
|
282
290
|
});
|
|
283
291
|
}
|
|
284
292
|
}
|
package/lib/language.js
CHANGED
|
@@ -118,7 +118,8 @@ exports.errors = {
|
|
|
118
118
|
positive: 'must be a positive number',
|
|
119
119
|
precision: 'must have no more than {{limit}} decimal places',
|
|
120
120
|
ref: 'references "{{ref}}" which is not a number',
|
|
121
|
-
multiple: 'must be a multiple of {{multiple}}'
|
|
121
|
+
multiple: 'must be a multiple of {{multiple}}',
|
|
122
|
+
port: 'must be a valid port'
|
|
122
123
|
},
|
|
123
124
|
string: {
|
|
124
125
|
base: 'must be a string',
|
|
@@ -142,6 +143,7 @@ exports.errors = {
|
|
|
142
143
|
isoDate: 'must be a valid ISO 8601 date',
|
|
143
144
|
guid: 'must be a valid GUID',
|
|
144
145
|
hex: 'must only contain hexadecimal characters',
|
|
146
|
+
hexAlign: 'hex decoded representation must be byte aligned',
|
|
145
147
|
base64: 'must be a valid base64 string',
|
|
146
148
|
hostname: 'must be a valid hostname',
|
|
147
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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Load modules
|
|
4
4
|
|
|
5
5
|
const Hoek = require('hoek');
|
|
6
|
+
const Settings = require('./settings');
|
|
6
7
|
const Ref = require('../../ref');
|
|
7
8
|
const Errors = require('../../errors');
|
|
8
9
|
let Alternatives = null; // Delay-loaded to prevent circular dependencies
|
|
@@ -104,10 +105,10 @@ module.exports = internals.Any = class {
|
|
|
104
105
|
obj.isJoi = true;
|
|
105
106
|
obj._currentJoi = this._currentJoi;
|
|
106
107
|
obj._type = this._type;
|
|
107
|
-
obj._settings =
|
|
108
|
+
obj._settings = this._settings;
|
|
108
109
|
obj._baseType = this._baseType;
|
|
109
|
-
obj._valids =
|
|
110
|
-
obj._invalids =
|
|
110
|
+
obj._valids = this._valids.slice();
|
|
111
|
+
obj._invalids = this._invalids.slice();
|
|
111
112
|
obj._tests = this._tests.slice();
|
|
112
113
|
obj._refs = this._refs.slice();
|
|
113
114
|
obj._flags = Hoek.clone(this._flags);
|
|
@@ -150,7 +151,7 @@ module.exports = internals.Any = class {
|
|
|
150
151
|
obj = tmpObj;
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
obj._settings = obj._settings ?
|
|
154
|
+
obj._settings = obj._settings ? Settings.concat(obj._settings, schema._settings) : schema._settings;
|
|
154
155
|
obj._valids.merge(schema._valids, schema._invalids);
|
|
155
156
|
obj._invalids.merge(schema._invalids, schema._valids);
|
|
156
157
|
obj._tests = obj._tests.concat(schema._tests);
|
|
@@ -218,15 +219,16 @@ module.exports = internals.Any = class {
|
|
|
218
219
|
this.checkOptions(options);
|
|
219
220
|
|
|
220
221
|
const obj = this.clone();
|
|
221
|
-
obj._settings =
|
|
222
|
+
obj._settings = Settings.concat(obj._settings, options);
|
|
222
223
|
return obj;
|
|
223
224
|
}
|
|
224
225
|
|
|
225
226
|
strict(isStrict) {
|
|
226
227
|
|
|
227
228
|
const obj = this.clone();
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
|
|
230
|
+
const convert = isStrict === undefined ? false : !isStrict;
|
|
231
|
+
obj._settings = Settings.concat(obj._settings, { convert });
|
|
230
232
|
return obj;
|
|
231
233
|
}
|
|
232
234
|
|
|
@@ -480,7 +482,7 @@ module.exports = internals.Any = class {
|
|
|
480
482
|
state = state || { key: '', path: [], parent: null, reference };
|
|
481
483
|
|
|
482
484
|
if (this._settings) {
|
|
483
|
-
options =
|
|
485
|
+
options = Settings.concat(options, this._settings);
|
|
484
486
|
}
|
|
485
487
|
|
|
486
488
|
let errors = [];
|
|
@@ -593,7 +595,7 @@ module.exports = internals.Any = class {
|
|
|
593
595
|
}
|
|
594
596
|
|
|
595
597
|
if (this._invalids.has(value, state, options, this._flags.insensitive)) {
|
|
596
|
-
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));
|
|
597
599
|
if (options.abortEarly ||
|
|
598
600
|
value === undefined) { // No reason to keep validating missing value
|
|
599
601
|
|
|
@@ -621,7 +623,7 @@ module.exports = internals.Any = class {
|
|
|
621
623
|
}
|
|
622
624
|
|
|
623
625
|
if (this._invalids.has(value, state, options, this._flags.insensitive)) {
|
|
624
|
-
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));
|
|
625
627
|
if (options.abortEarly) {
|
|
626
628
|
return finish();
|
|
627
629
|
}
|
|
@@ -632,13 +634,13 @@ module.exports = internals.Any = class {
|
|
|
632
634
|
// Required values did not match
|
|
633
635
|
|
|
634
636
|
if (this._flags.allowOnly) {
|
|
635
|
-
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));
|
|
636
638
|
if (options.abortEarly) {
|
|
637
639
|
return finish();
|
|
638
640
|
}
|
|
639
641
|
}
|
|
640
642
|
|
|
641
|
-
//
|
|
643
|
+
// Validate tests
|
|
642
644
|
|
|
643
645
|
for (let i = 0; i < this._tests.length; ++i) {
|
|
644
646
|
const test = this._tests[i];
|
|
@@ -663,7 +665,7 @@ module.exports = internals.Any = class {
|
|
|
663
665
|
this.checkOptions(options);
|
|
664
666
|
}
|
|
665
667
|
|
|
666
|
-
const settings =
|
|
668
|
+
const settings = Settings.concat(internals.defaults, options);
|
|
667
669
|
const result = this._validate(value, null, settings);
|
|
668
670
|
const errors = Errors.process(result.errors, value);
|
|
669
671
|
|
|
@@ -880,33 +882,3 @@ internals._try = function (fn, args) {
|
|
|
880
882
|
error: err
|
|
881
883
|
};
|
|
882
884
|
};
|
|
883
|
-
|
|
884
|
-
internals.concatSettings = function (target, source) {
|
|
885
|
-
|
|
886
|
-
// Used to avoid cloning context
|
|
887
|
-
|
|
888
|
-
if (!target &&
|
|
889
|
-
!source) {
|
|
890
|
-
|
|
891
|
-
return null;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
const obj = Object.assign({}, target);
|
|
895
|
-
|
|
896
|
-
if (source) {
|
|
897
|
-
const sKeys = Object.keys(source);
|
|
898
|
-
for (let i = 0; i < sKeys.length; ++i) {
|
|
899
|
-
const key = sKeys[i];
|
|
900
|
-
if (key !== 'language' ||
|
|
901
|
-
!obj.hasOwnProperty(key)) {
|
|
902
|
-
|
|
903
|
-
obj[key] = source[key];
|
|
904
|
-
}
|
|
905
|
-
else {
|
|
906
|
-
obj[key] = Hoek.applyToDefaults(obj[key], source[key]);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
return obj;
|
|
912
|
-
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Load modules
|
|
4
|
+
|
|
5
|
+
const Hoek = require('hoek');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Declare internals
|
|
9
|
+
|
|
10
|
+
const internals = {};
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
exports.concat = function (target, source) {
|
|
14
|
+
|
|
15
|
+
if (!source) {
|
|
16
|
+
return target;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const obj = Object.assign({}, target);
|
|
20
|
+
|
|
21
|
+
const sKeys = Object.keys(source);
|
|
22
|
+
for (let i = 0; i < sKeys.length; ++i) {
|
|
23
|
+
const key = sKeys[i];
|
|
24
|
+
if (key !== 'language' ||
|
|
25
|
+
!obj.hasOwnProperty(key)) {
|
|
26
|
+
|
|
27
|
+
obj[key] = source[key];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
obj[key] = Hoek.applyToDefaults(obj[key], source[key]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return obj;
|
|
35
|
+
};
|
package/lib/types/array/index.js
CHANGED
|
@@ -123,6 +123,8 @@ internals.Array = class extends Any {
|
|
|
123
123
|
return errors;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
ordereds.shift();
|
|
127
|
+
|
|
126
128
|
continue;
|
|
127
129
|
}
|
|
128
130
|
|
|
@@ -139,6 +141,8 @@ internals.Array = class extends Any {
|
|
|
139
141
|
return errors;
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
ordereds.shift();
|
|
145
|
+
|
|
142
146
|
break;
|
|
143
147
|
}
|
|
144
148
|
}
|
|
@@ -491,10 +495,10 @@ internals.Array = class extends Any {
|
|
|
491
495
|
return this._test('unique', settings, function (value, state, options) {
|
|
492
496
|
|
|
493
497
|
const found = {
|
|
494
|
-
string:
|
|
495
|
-
number:
|
|
496
|
-
undefined:
|
|
497
|
-
boolean:
|
|
498
|
+
string: Object.create(null),
|
|
499
|
+
number: Object.create(null),
|
|
500
|
+
undefined: Object.create(null),
|
|
501
|
+
boolean: Object.create(null),
|
|
498
502
|
object: new Map(),
|
|
499
503
|
function: new Map(),
|
|
500
504
|
custom: new Map()
|
|
@@ -128,6 +128,18 @@ internals.Number = class extends Any {
|
|
|
128
128
|
return obj;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
port() {
|
|
132
|
+
|
|
133
|
+
return this._test('port', undefined, function (value, state, options) {
|
|
134
|
+
|
|
135
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 65535) {
|
|
136
|
+
return this.createError('number.port', { value }, state, options);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return value;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
131
143
|
};
|
|
132
144
|
|
|
133
145
|
|
|
@@ -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
|
}
|
|
@@ -366,6 +367,15 @@ internals.Object = class extends Any {
|
|
|
366
367
|
return obj;
|
|
367
368
|
}
|
|
368
369
|
|
|
370
|
+
append(schema) {
|
|
371
|
+
// Skip any changes
|
|
372
|
+
if (schema === null || schema === undefined || Object.keys(schema).length === 0) {
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return this.keys(schema);
|
|
377
|
+
}
|
|
378
|
+
|
|
369
379
|
unknown(allow) {
|
|
370
380
|
|
|
371
381
|
const value = allow !== false;
|
|
@@ -459,11 +469,15 @@ internals.Object = class extends Any {
|
|
|
459
469
|
|
|
460
470
|
with(key, peers) {
|
|
461
471
|
|
|
472
|
+
Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');
|
|
473
|
+
|
|
462
474
|
return this._dependency('with', key, peers);
|
|
463
475
|
}
|
|
464
476
|
|
|
465
477
|
without(key, peers) {
|
|
466
478
|
|
|
479
|
+
Hoek.assert(arguments.length === 2, 'Invalid number of arguments, expected 2.');
|
|
480
|
+
|
|
467
481
|
return this._dependency('without', key, peers);
|
|
468
482
|
}
|
|
469
483
|
|
|
@@ -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 = {}) {
|