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 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.2.0/API.md).
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
- constructor() {
6
+ const internals = {};
8
7
 
9
- this._set = [];
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
- if (!Ref.isRef(value) && this.has(value, null, null, false)) {
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.push(value);
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 (let i = 0; i < add._set.length; ++i) {
30
- this.add(add._set[i]);
68
+ for (const item of add._set) {
69
+ this.add(item);
31
70
  }
32
71
 
33
- for (let i = 0; i < remove._set.length; ++i) {
34
- this.remove(remove._set[i]);
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 = this._set.filter((item) => value !== item);
81
+ this._set.delete(value);
43
82
  return this;
44
83
  }
45
84
 
46
85
  has(value, state, options, insensitive) {
47
86
 
48
- for (let i = 0; i < this._set.length; ++i) {
49
- let items = this._set[i];
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
- if (state && Ref.isRef(items)) { // Only resolve references if there is a state, otherwise it's a merge
52
- items = items(state.reference || state.parent, options);
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
- if (!Array.isArray(items)) {
56
- items = [items];
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
- for (let j = 0; j < items.length; ++j) {
60
- const item = items[j];
61
- if (typeof value !== typeof item) {
62
- continue;
63
- }
125
+ return check(item);
126
+ };
64
127
 
65
- if (value === item ||
66
- (value instanceof Date && item instanceof Date && value.getTime() === item.getTime()) ||
67
- (insensitive && typeof value === 'string' && value.toLowerCase() === item.toLowerCase()) ||
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
- return true;
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 (let i = 0; i < this._set.length; ++i) {
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.slice();
162
+ return Array.from(this._set);
94
163
  }
95
164
 
96
165
  slice() {
97
166
 
98
- const newSet = new Set();
99
- newSet._set = this._set.slice();
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 newSet = new Set();
107
- newSet._set = this._set.concat(source._set);
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
  };
@@ -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', null, state, options));
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', null, state, options));
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
- // Helper.validate tests
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 = Hoek.mapToObject(Object.keys(target));
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 unprocessed[key];
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
- let unprocessedKeys = Object.keys(unprocessed);
234
- if (unprocessedKeys.length &&
235
- this._inner.patterns.length) {
233
+ if (unprocessed.size && this._inner.patterns.length) {
236
234
 
237
- for (let i = 0; i < unprocessedKeys.length; ++i) {
238
- const key = unprocessedKeys[i];
239
- const localState = { key, path: state.path.concat(key), parent: target, reference: state.reference };
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 j = 0; j < this._inner.patterns.length; ++j) {
243
- const pattern = this._inner.patterns[j];
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 unprocessed[key];
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', { key, child: pattern.rule._getLabel(key), reason: result.errors }, localState, options));
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) && unprocessedKeys.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 (let i = 0; i < unprocessedKeys.length; ++i) {
275
- const key = unprocessedKeys[i];
276
-
278
+ for (const key of unprocessed) {
277
279
  if (stripUnknown) {
278
280
  delete target[key];
279
- delete unprocessed[key];
281
+ unprocessed.delete(key);
280
282
  }
281
283
  else if (typeof target[key] === 'function') {
282
- delete unprocessed[key];
284
+ unprocessed.delete(key);
283
285
  }
284
286
  }
285
-
286
- unprocessedKeys = Object.keys(unprocessed);
287
287
  }
288
288
 
289
- if (unprocessedKeys.length &&
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 (let i = 0; i < unprocessedKeys.length; ++i) {
293
- const unprocessedKey = unprocessedKeys[i];
294
- errors.push(this.createError('object.allowUnknown', { child: unprocessedKey }, { key: unprocessedKey, path: state.path.concat(unprocessedKey) }, options, {}));
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
- return this._test('hex', regex, function (value, state, options) {
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 = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "13.2.0",
4
+ "version": "13.3.0",
5
5
  "homepage": "https://github.com/hapijs/joi",
6
6
  "repository": "git://github.com/hapijs/joi",
7
7
  "main": "lib/index.js",