joi 6.8.1 → 6.10.1

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/.travis.yml CHANGED
@@ -4,5 +4,6 @@ node_js:
4
4
  - "0.10"
5
5
  - "4.0"
6
6
  - "4"
7
+ - "5"
7
8
 
8
9
  sudo: false
package/API.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <!-- version -->
2
- # 6.8.1 API Reference
2
+ # 6.10.1 API Reference
3
3
  <!-- versionstop -->
4
4
 
5
5
  <img src="https://raw.github.com/hapijs/joi/master/images/validation.png" align="right" />
@@ -38,6 +38,7 @@
38
38
  - [`array.sparse(enabled)`](#arraysparseenabled)
39
39
  - [`array.single(enabled)`](#arraysingleenabled)
40
40
  - [`array.items(type)`](#arrayitemstype)
41
+ - [`array.ordered(type)`](#arrayorderedtype)
41
42
  - [`array.min(limit)`](#arrayminlimit)
42
43
  - [`array.max(limit)`](#arraymaxlimit)
43
44
  - [`array.length(limit)`](#arraylengthlimit)
@@ -109,6 +110,7 @@
109
110
  - [`alternatives.try(schemas)`](#alternativestryschemas)
110
111
  - [`alternatives.when(ref, options)`](#alternativeswhenref-options)
111
112
  - [`ref(key, [options])`](#refkey-options)
113
+ - [Errors](#errors)
112
114
 
113
115
  <!-- tocstop -->
114
116
 
@@ -132,9 +134,9 @@ Validates a value using the given schema and options where:
132
134
  `validate()` and not using `any.options()`.
133
135
  - `noDefaults` - when `true`, do not apply default values. Defaults to `false`.
134
136
  - `callback` - the optional synchronous callback method using the signature `function(err, value)` where:
135
- - `err` - if validation failed, the error reason, otherwise `null`.
137
+ - `err` - if validation failed, the [error](#errors) reason, otherwise `null`.
136
138
  - `value` - the validated value with any type conversions and other modifiers applied (the input is left unchanged). `value` can be
137
- incomplete if validation failed and `abortEarly` is `true`. If callback is not provided, then returns an object with error
139
+ incomplete if validation failed and `abortEarly` is `true`. If callback is not provided, then returns an object with [error](#errors)
138
140
  and value properties.
139
141
 
140
142
  ```javascript
@@ -182,7 +184,7 @@ var schema = Joi.alternatives().try([
182
184
 
183
185
  ### `assert(value, schema, [message])`
184
186
 
185
- Validates a value against a schema and throws if validation fails where:
187
+ Validates a value against a schema and [throws](#errors) if validation fails where:
186
188
  - `value` - the value to validate.
187
189
  - `schema` - the schema object.
188
190
  - `message` - optional message string prefix added in front of the error message. may also be an Error object.
@@ -193,7 +195,7 @@ Joi.assert('x', Joi.number());
193
195
 
194
196
  ### `attempt(value, schema, [message])`
195
197
 
196
- Validates a value against a schema, returns valid object, and throws if validation fails where:
198
+ Validates a value against a schema, returns valid object, and [throws](#errors) if validation fails where:
197
199
  - `value` - the value to validate.
198
200
  - `schema` - the schema object.
199
201
  - `message` - optional message string prefix added in front of the error message. may also be an Error object.
@@ -547,6 +549,20 @@ var schema = Joi.array().items(Joi.string().valid('not allowed').forbidden(), Jo
547
549
  var schema = Joi.array().items(Joi.string().label('My string').required(), Joi.number().required()); // If this fails it can result in `[ValidationError: "value" does not contain [My string] and 1 other required value(s)]`
548
550
  ```
549
551
 
552
+ #### `array.ordered(type)`
553
+
554
+ List the types in sequence order for the array values where:
555
+ - `type` - a **joi** schema object to validate against each array item in sequence order. `type` can be an array of values, or multiple values can be passed as individual arguments.
556
+
557
+ If a given type is `.required()` then there must be a matching item with the same index position in the array.
558
+ Errors will contain the number of items that didn't match. Any unmatched item having a [label](#anylabelname) will be mentioned explicitly.
559
+
560
+ ```javascript
561
+ var schema = Joi.array().ordered(Joi.string().required(), Joi.number().required()); // array must have first item as string and second item as number
562
+ var schema = Joi.array().ordered(Joi.string().required()).items(Joi.number().required()); // array must have first item as string and 1 or more subsequent items as number
563
+ var schema = Joi.array().ordered(Joi.string().required(), Joi.number()); // array must have first item as string and optionally second item as number
564
+ ```
565
+
550
566
  #### `array.min(limit)`
551
567
 
552
568
  Specifies the minimum number of items in the array where:
@@ -1484,4 +1500,16 @@ var schema = Joi.object().keys({
1484
1500
  });
1485
1501
 
1486
1502
  Joi.validate({ a: 5, b: { c: 5 } }, schema, { context: { x: 5 } }, function (err, value) {});
1487
- ```
1503
+ ```
1504
+
1505
+ ## Errors
1506
+
1507
+ Joi throws classical javascript `Error`s containing :
1508
+ - `name` - `ValidationError`.
1509
+ - `details` - an array of errors :
1510
+ - `message` - string with a description of the error.
1511
+ - `path` - dotted path to the key where the error happened.
1512
+ - `type` - type of the error.
1513
+ - `context` - object providing context of the error.
1514
+ - `annotate` - function that returns a string with an annotated version of the object pointing at the places where errors occured.
1515
+ - `_object` - the original object to validate.
package/lib/any.js CHANGED
@@ -256,6 +256,8 @@ internals.Any.prototype._allow = function () {
256
256
  var values = Hoek.flatten(Array.prototype.slice.call(arguments));
257
257
  for (var i = 0, il = values.length; i < il; ++i) {
258
258
  var value = values[i];
259
+
260
+ Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
259
261
  this._invalids.remove(value);
260
262
  this._valids.add(value, this._refs);
261
263
  }
@@ -286,6 +288,8 @@ internals.Any.prototype.invalid = internals.Any.prototype.disallow = internals.A
286
288
  var values = Hoek.flatten(Array.prototype.slice.call(arguments));
287
289
  for (var i = 0, il = values.length; i < il; ++i) {
288
290
  value = values[i];
291
+
292
+ Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
289
293
  obj._valids.remove(value);
290
294
  obj._invalids.add(value, this._refs);
291
295
  }
@@ -526,7 +530,7 @@ internals.Any.prototype._validate = function (value, state, options, reference)
526
530
  }
527
531
  }
528
532
  else {
529
- finalValue = self._flags.default;
533
+ finalValue = Hoek.clone(self._flags.default);
530
534
  }
531
535
  }
532
536
 
package/lib/array.js CHANGED
@@ -29,6 +29,7 @@ internals.Array = function () {
29
29
  Any.call(this);
30
30
  this._type = 'array';
31
31
  this._inner.items = [];
32
+ this._inner.ordereds = [];
32
33
  this._inner.inclusions = [];
33
34
  this._inner.exclusions = [];
34
35
  this._inner.requireds = [];
@@ -106,6 +107,7 @@ internals.checkItems = function (items, wasArray, state, options) {
106
107
  var errored;
107
108
 
108
109
  var requireds = this._inner.requireds.slice();
110
+ var ordereds = this._inner.ordereds.slice();
109
111
  var inclusions = this._inner.inclusions.concat(requireds);
110
112
 
111
113
  for (var v = 0, vl = items.length; v < vl; ++v) {
@@ -131,6 +133,7 @@ internals.checkItems = function (items, wasArray, state, options) {
131
133
 
132
134
  for (var i = 0, il = this._inner.exclusions.length; i < il; ++i) {
133
135
  res = this._inner.exclusions[i]._validate(item, localState, {}); // Not passing options to use defaults
136
+
134
137
  if (!res.errors) {
135
138
  errors.push(Errors.create(wasArray ? 'array.excludes' : 'array.excludesSingle', { pos: v, value: item }, { key: state.key, path: localState.path }, options));
136
139
  errored = true;
@@ -147,6 +150,38 @@ internals.checkItems = function (items, wasArray, state, options) {
147
150
  continue;
148
151
  }
149
152
 
153
+ // Ordered
154
+ if (this._inner.ordereds.length) {
155
+ if (ordereds.length > 0) {
156
+ var ordered = ordereds.shift();
157
+ res = ordered._validate(item, localState, options);
158
+ if (!res.errors) {
159
+ if (ordered._flags.strip) {
160
+ internals.fastSplice(items, v);
161
+ --v;
162
+ --vl;
163
+ }
164
+ else {
165
+ items[v] = res.value;
166
+ }
167
+ }
168
+ else {
169
+ errors.push(Errors.create('array.ordered', { pos: v, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options));
170
+ if (options.abortEarly) {
171
+ return errors;
172
+ }
173
+ }
174
+ continue;
175
+ }
176
+ else if (!this._inner.items.length) {
177
+ errors.push(Errors.create('array.orderedLength', { pos: v, limit: this._inner.ordereds.length }, { key: state.key, path: localState.path }, options));
178
+ if (options.abortEarly) {
179
+ return errors;
180
+ }
181
+ continue;
182
+ }
183
+ }
184
+
150
185
  // Requireds
151
186
 
152
187
  var requiredChecks = [];
@@ -238,10 +273,13 @@ internals.checkItems = function (items, wasArray, state, options) {
238
273
  internals.fillMissedErrors(errors, requireds, state, options);
239
274
  }
240
275
 
276
+ if (ordereds.length) {
277
+ internals.fillOrderedErrors(errors, ordereds, state, options);
278
+ }
279
+
241
280
  return errors.length ? errors : null;
242
281
  };
243
282
 
244
-
245
283
  internals.fillMissedErrors = function (errors, requireds, state, options) {
246
284
 
247
285
  var knownMisses = [];
@@ -269,11 +307,34 @@ internals.fillMissedErrors = function (errors, requireds, state, options) {
269
307
  }
270
308
  };
271
309
 
310
+ internals.fillOrderedErrors = function (errors, ordereds, state, options) {
311
+
312
+ var requiredOrdereds = [];
313
+
314
+ for (var i = 0, il = ordereds.length; i < il; ++i) {
315
+ var presence = Hoek.reach(ordereds[i], '_flags.presence');
316
+ if (presence === 'required') {
317
+ requiredOrdereds.push(ordereds[i]);
318
+ }
319
+ }
320
+
321
+ if (requiredOrdereds.length) {
322
+ internals.fillMissedErrors(errors, requiredOrdereds, state, options);
323
+ }
324
+ };
272
325
 
273
326
  internals.Array.prototype.describe = function () {
274
327
 
275
328
  var description = Any.prototype.describe.call(this);
276
329
 
330
+ if (this._inner.ordereds.length) {
331
+ description.orderedItems = [];
332
+
333
+ for (var o = 0, ol = this._inner.ordereds.length; o < ol; ++o) {
334
+ description.orderedItems.push(this._inner.ordereds[o].describe());
335
+ }
336
+ }
337
+
277
338
  if (this._inner.items.length) {
278
339
  description.items = [];
279
340
 
@@ -290,9 +351,22 @@ internals.Array.prototype.items = function () {
290
351
 
291
352
  var obj = this.clone();
292
353
 
293
- Hoek.flatten(Array.prototype.slice.call(arguments)).forEach(function (type) {
354
+ Hoek.flatten(Array.prototype.slice.call(arguments)).forEach(function (type, index) {
355
+
356
+ try {
357
+ type = Cast.schema(type);
358
+ }
359
+ catch (castErr) {
360
+ if (castErr.hasOwnProperty('path')) {
361
+ castErr.path = index + '.' + castErr.path;
362
+ }
363
+ else {
364
+ castErr.path = index;
365
+ }
366
+ castErr.message += '(' + castErr.path + ')';
367
+ throw castErr;
368
+ }
294
369
 
295
- type = Cast.schema(type);
296
370
  obj._inner.items.push(type);
297
371
 
298
372
  if (type._flags.presence === 'required') {
@@ -310,6 +384,32 @@ internals.Array.prototype.items = function () {
310
384
  };
311
385
 
312
386
 
387
+ internals.Array.prototype.ordered = function () {
388
+
389
+ var obj = this.clone();
390
+
391
+ Hoek.flatten(Array.prototype.slice.call(arguments)).forEach(function (type, index) {
392
+
393
+ try {
394
+ type = Cast.schema(type);
395
+ }
396
+ catch (castErr) {
397
+ if (castErr.hasOwnProperty('path')) {
398
+ castErr.path = index + '.' + castErr.path;
399
+ }
400
+ else {
401
+ castErr.path = index;
402
+ }
403
+ castErr.message += '(' + castErr.path + ')';
404
+ throw castErr;
405
+ }
406
+ obj._inner.ordereds.push(type);
407
+ });
408
+
409
+ return obj;
410
+ };
411
+
412
+
313
413
  internals.Array.prototype.min = function (limit) {
314
414
 
315
415
  Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
package/lib/index.js CHANGED
@@ -95,20 +95,28 @@ internals.root = function () {
95
95
  }
96
96
 
97
97
  var options = count === 3 ? arguments[2] : {};
98
- var schema = Cast.schema(arguments[1]);
98
+ var schema = root.compile(arguments[1]);
99
99
 
100
100
  return schema._validateWithOptions(value, options, callback);
101
101
  };
102
102
 
103
103
  root.describe = function () {
104
104
 
105
- var schema = arguments.length ? Cast.schema(arguments[0]) : any;
105
+ var schema = arguments.length ? root.compile(arguments[0]) : any;
106
106
  return schema.describe();
107
107
  };
108
108
 
109
109
  root.compile = function (schema) {
110
110
 
111
- return Cast.schema(schema);
111
+ try {
112
+ return Cast.schema(schema);
113
+ }
114
+ catch (err) {
115
+ if (err.hasOwnProperty('path')) {
116
+ err.message += '(' + err.path + ')';
117
+ }
118
+ throw err;
119
+ }
112
120
  };
113
121
 
114
122
  root.assert = function (value, schema, message) {
@@ -122,11 +130,13 @@ internals.root = function () {
122
130
  var error = result.error;
123
131
  if (error) {
124
132
  if (!message) {
125
- throw new Error(error.annotate());
133
+ error.message = error.annotate();
134
+ throw error;
126
135
  }
127
136
 
128
137
  if (!(message instanceof Error)) {
129
- throw new Error(message + ' ' + error.annotate());
138
+ error.message = message + ' ' + error.annotate();
139
+ throw error;
130
140
  }
131
141
 
132
142
  throw message;
package/lib/language.js CHANGED
@@ -37,6 +37,8 @@ exports.errors = {
37
37
  min: 'must contain at least {{limit}} items',
38
38
  max: 'must contain less than or equal to {{limit}} items',
39
39
  length: 'must contain {{limit}} items',
40
+ ordered: 'at position {{pos}} fails because {{reason}}',
41
+ orderedLength: 'at position {{pos}} fails because array must contain at most {{limit}} items',
40
42
  sparse: 'must not be a sparse array',
41
43
  unique: 'position {{pos}} contains a duplicate value'
42
44
  },
package/lib/object.js CHANGED
@@ -231,7 +231,7 @@ internals.Object.prototype._base = function (value, state, options) {
231
231
  (this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {
232
232
 
233
233
  for (var e = 0, el = unprocessedKeys.length; e < el; ++e) {
234
- errors.push(Errors.create('object.allowUnknown', null, { key: unprocessedKeys[e], path: state.path + '.' + unprocessedKeys[e] }, options));
234
+ errors.push(Errors.create('object.allowUnknown', null, { key: unprocessedKeys[e], path: state.path + (state.path ? '.' : '') + unprocessedKeys[e] }, options));
235
235
  }
236
236
  }
237
237
  }
@@ -296,8 +296,19 @@ internals.Object.prototype.keys = function (schema) {
296
296
  for (var c = 0, cl = children.length; c < cl; ++c) {
297
297
  var key = children[c];
298
298
  child = schema[key];
299
- var cast = Cast.schema(child);
300
- topo.add({ key: key, schema: cast }, { after: cast._refs, group: key });
299
+ try {
300
+ var cast = Cast.schema(child);
301
+ topo.add({ key: key, schema: cast }, { after: cast._refs, group: key });
302
+ }
303
+ catch (castErr) {
304
+ if (castErr.hasOwnProperty('path')) {
305
+ castErr.path = key + '.' + castErr.path;
306
+ }
307
+ else {
308
+ castErr.path = key;
309
+ }
310
+ throw castErr;
311
+ }
301
312
  }
302
313
 
303
314
  obj._inner.children = topo.nodes;
@@ -366,8 +377,20 @@ internals.Object.prototype.pattern = function (pattern, schema) {
366
377
 
367
378
  pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
368
379
 
380
+ try {
381
+ schema = Cast.schema(schema);
382
+ }
383
+ catch (castErr) {
384
+ if (castErr.hasOwnProperty('path')) {
385
+ castErr.message += '(' + castErr.path + ')';
386
+ }
387
+
388
+ throw castErr;
389
+ }
390
+
391
+
369
392
  var obj = this.clone();
370
- obj._inner.patterns.push({ regex: pattern, rule: Cast.schema(schema) });
393
+ obj._inner.patterns.push({ regex: pattern, rule: schema });
371
394
  return obj;
372
395
  };
373
396
 
@@ -682,7 +705,18 @@ internals.Object.prototype.assert = function (ref, schema, message) {
682
705
  Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead');
683
706
  message = message || 'pass the assertion test';
684
707
 
685
- var cast = Cast.schema(schema);
708
+ var cast;
709
+ try {
710
+ cast = Cast.schema(schema);
711
+ }
712
+ catch (castErr) {
713
+ if (castErr.hasOwnProperty('path')) {
714
+ castErr.message += '(' + castErr.path + ')';
715
+ }
716
+
717
+ throw castErr;
718
+ }
719
+
686
720
  var key = ref.path[ref.path.length - 1];
687
721
  var path = ref.path.join('.');
688
722
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "6.8.1",
4
+ "version": "6.10.1",
5
5
  "repository": "git://github.com/hapijs/joi",
6
6
  "main": "lib/index.js",
7
7
  "keywords": [
package/test/any.js CHANGED
@@ -552,6 +552,31 @@ describe('any', function () {
552
552
  ], done);
553
553
  });
554
554
 
555
+ it('should set default value as a clone', function (done) {
556
+
557
+ var defaultValue = { bar: 'val' };
558
+ var schema = Joi.object({ foo: Joi.object().default(defaultValue) });
559
+ var input = {};
560
+
561
+ schema.validate(input, function (err, value) {
562
+
563
+ expect(err).to.not.exist();
564
+ expect(value.foo).to.not.equal(defaultValue);
565
+ expect(value.foo).to.only.deep.include({ bar: 'val' });
566
+
567
+ value.foo.bar = 'mutated';
568
+
569
+ schema.validate(input, function (err2, value2) {
570
+
571
+ expect(err2).to.not.exist();
572
+ expect(value2.foo).to.not.equal(defaultValue);
573
+ expect(value2.foo).to.only.deep.include({ bar: 'val' });
574
+
575
+ done();
576
+ });
577
+ });
578
+ });
579
+
555
580
  it('should not apply default values if the noDefaults option is enquire', function (done) {
556
581
 
557
582
  var schema = Joi.object({
@@ -1497,8 +1522,72 @@ describe('any', function () {
1497
1522
 
1498
1523
  it('strips undefined', function (done) {
1499
1524
 
1500
- var b = Joi.any().allow(undefined);
1501
- expect(b._valids.values({ stripUndefined: true })).to.not.include(undefined);
1525
+ var any = Joi.any().clone();
1526
+ any._valids.add(undefined);
1527
+ expect(any._valids.values({ stripUndefined: true })).to.not.include(undefined);
1528
+ done();
1529
+ });
1530
+ });
1531
+
1532
+ describe('#allow', function () {
1533
+
1534
+ it('allows valid values to be set', function (done) {
1535
+
1536
+ expect(function () {
1537
+
1538
+ Joi.any().allow(true, 1, 'hello', new Date());
1539
+ }).not.to.throw();
1540
+ done();
1541
+ });
1542
+
1543
+ it('throws when passed undefined', function (done) {
1544
+
1545
+ expect(function () {
1546
+
1547
+ Joi.any().allow(undefined);
1548
+ }).to.throw(Error, 'Cannot call allow/valid/invalid with undefined');
1549
+ done();
1550
+ });
1551
+ });
1552
+
1553
+ describe('#valid', function () {
1554
+
1555
+ it('allows valid values to be set', function (done) {
1556
+
1557
+ expect(function () {
1558
+
1559
+ Joi.any().valid(true, 1, 'hello', new Date());
1560
+ }).not.to.throw();
1561
+ done();
1562
+ });
1563
+
1564
+ it('throws when passed undefined', function (done) {
1565
+
1566
+ expect(function () {
1567
+
1568
+ Joi.any().valid(undefined);
1569
+ }).to.throw(Error, 'Cannot call allow/valid/invalid with undefined');
1570
+ done();
1571
+ });
1572
+ });
1573
+
1574
+ describe('#invalid', function () {
1575
+
1576
+ it('allows invalid values to be set', function (done) {
1577
+
1578
+ expect(function () {
1579
+
1580
+ Joi.any().valid(true, 1, 'hello', new Date());
1581
+ }).not.to.throw();
1582
+ done();
1583
+ });
1584
+
1585
+ it('throws when passed undefined', function (done) {
1586
+
1587
+ expect(function () {
1588
+
1589
+ Joi.any().invalid(undefined);
1590
+ }).to.throw('Cannot call allow/valid/invalid with undefined');
1502
1591
  done();
1503
1592
  });
1504
1593
  });
package/test/array.js CHANGED
@@ -85,6 +85,29 @@ describe('array', function () {
85
85
  });
86
86
  });
87
87
 
88
+ it('shows path to errors in array items', function (done) {
89
+
90
+ expect(function () {
91
+
92
+ Joi.array().items({
93
+ a: {
94
+ b: {
95
+ c: {
96
+ d: undefined
97
+ }
98
+ }
99
+ }
100
+ });
101
+ }).to.throw(Error, 'Invalid schema content: (0.a.b.c.d)');
102
+
103
+ expect(function () {
104
+
105
+ Joi.array().items({ foo: 'bar' }, undefined);
106
+ }).to.throw(Error, 'Invalid schema content: (1)');
107
+
108
+ done();
109
+ });
110
+
88
111
  it('allows zero size', function (done) {
89
112
 
90
113
  var schema = Joi.object({
@@ -569,7 +592,7 @@ describe('array', function () {
569
592
  done();
570
593
  });
571
594
 
572
- it('returns an includes array only if includes are specified', function (done) {
595
+ it('returns an items array only if items are specified', function (done) {
573
596
 
574
597
  var schema = Joi.array().items().max(5);
575
598
  var desc = schema.describe();
@@ -577,14 +600,19 @@ describe('array', function () {
577
600
  done();
578
601
  });
579
602
 
580
- it('returns a recursively defined array of includes when specified', function (done) {
603
+ it('returns a recursively defined array of items when specified', function (done) {
581
604
 
582
- var schema = Joi.array().items(Joi.number(), Joi.string()).items(Joi.boolean().forbidden());
605
+ var schema = Joi.array()
606
+ .items(Joi.number(), Joi.string())
607
+ .items(Joi.boolean().forbidden())
608
+ .ordered(Joi.number(), Joi.string())
609
+ .ordered(Joi.string().required());
583
610
  var desc = schema.describe();
584
611
  expect(desc.items).to.have.length(3);
585
612
  expect(desc).to.deep.equal({
586
613
  type: 'array',
587
614
  flags: { sparse: false },
615
+ orderedItems: [{ type: 'number', invalids: [Infinity, -Infinity] }, { type: 'string', invalids: [''] }, { type: 'string', invalids: [''], flags: { presence: 'required' } }],
588
616
  items: [{ type: 'number', invalids: [Infinity, -Infinity] }, { type: 'string', invalids: [''] }, { type: 'boolean', flags: { presence: 'forbidden' } }]
589
617
  });
590
618
 
@@ -793,4 +821,229 @@ describe('array', function () {
793
821
  });
794
822
  });
795
823
  });
824
+
825
+ describe('#ordered', function () {
826
+
827
+ it('shows path to errors in array ordered items', function (done) {
828
+
829
+ expect(function () {
830
+
831
+ Joi.array().ordered({
832
+ a: {
833
+ b: {
834
+ c: {
835
+ d: undefined
836
+ }
837
+ }
838
+ }
839
+ });
840
+ }).to.throw(Error, 'Invalid schema content: (0.a.b.c.d)');
841
+
842
+ expect(function () {
843
+
844
+ Joi.array().ordered({ foo: 'bar' }, undefined);
845
+ }).to.throw(Error, 'Invalid schema content: (1)');
846
+
847
+ done();
848
+ });
849
+
850
+ it('validates input against items in order', function (done) {
851
+
852
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number().required()]);
853
+ var input = ['s1', 2];
854
+ schema.validate(input, function (err, value) {
855
+
856
+ expect(err).to.not.exist();
857
+ expect(value).to.deep.equal(['s1', 2]);
858
+ done();
859
+ });
860
+ });
861
+
862
+ it('validates input with optional item', function (done) {
863
+
864
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number().required(), Joi.number()]);
865
+ var input = ['s1', 2, 3];
866
+
867
+ schema.validate(input, function (err, value) {
868
+
869
+ expect(err).to.not.exist();
870
+ expect(value).to.deep.equal(['s1', 2, 3]);
871
+ done();
872
+ });
873
+ });
874
+
875
+ it('validates input without optional item', function (done) {
876
+
877
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number().required(), Joi.number()]);
878
+ var input = ['s1', 2];
879
+
880
+ schema.validate(input, function (err, value) {
881
+
882
+ expect(err).to.not.exist();
883
+ expect(value).to.deep.equal(['s1', 2]);
884
+ done();
885
+ });
886
+ });
887
+
888
+ it('validates input without optional item', function (done) {
889
+
890
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number().required(), Joi.number()]).sparse(true);
891
+ var input = ['s1', 2, undefined];
892
+
893
+ schema.validate(input, function (err, value) {
894
+
895
+ expect(err).to.not.exist();
896
+ expect(value).to.deep.equal(['s1', 2, undefined]);
897
+ done();
898
+ });
899
+ });
900
+
901
+ it('validates input without optional item in a sparse array', function (done) {
902
+
903
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number(), Joi.number().required()]).sparse(true);
904
+ var input = ['s1', undefined, 3];
905
+
906
+ schema.validate(input, function (err, value) {
907
+
908
+ expect(err).to.not.exist();
909
+ expect(value).to.deep.equal(['s1', undefined, 3]);
910
+ done();
911
+ });
912
+ });
913
+
914
+ it('validates when input matches ordered items and matches regular items', function (done) {
915
+
916
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number().required()]).items(Joi.number());
917
+ var input = ['s1', 2, 3, 4, 5];
918
+ schema.validate(input, function (err, value) {
919
+
920
+ expect(err).to.not.exist();
921
+ expect(value).to.deep.equal(['s1', 2, 3, 4, 5]);
922
+ done();
923
+ });
924
+ });
925
+
926
+ it('errors when input does not match ordered items', function (done) {
927
+
928
+ var schema = Joi.array().ordered([Joi.number().required(), Joi.string().required()]);
929
+ var input = ['s1', 2];
930
+ schema.validate(input, function (err, value) {
931
+
932
+ expect(err).to.exist();
933
+ expect(err.message).to.equal('"value" at position 0 fails because ["0" must be a number]');
934
+ done();
935
+ });
936
+ });
937
+
938
+ it('errors when input has more items than ordered items', function (done) {
939
+
940
+ var schema = Joi.array().ordered([Joi.number().required(), Joi.string().required()]);
941
+ var input = [1, 's2', 3];
942
+ schema.validate(input, function (err, value) {
943
+
944
+ expect(err).to.exist();
945
+ expect(err.message).to.equal('"value" at position 2 fails because array must contain at most 2 items');
946
+ done();
947
+ });
948
+ });
949
+
950
+ it('errors when input has more items than ordered items with abortEarly = false', function (done) {
951
+
952
+ var schema = Joi.array().ordered([Joi.string(), Joi.number()]).options({ abortEarly: false });
953
+ var input = [1, 2, 3, 4, 5];
954
+ schema.validate(input, function (err, value) {
955
+
956
+ expect(err).to.exist();
957
+ expect(err.message).to.equal('"value" at position 0 fails because ["0" must be a string]. "value" at position 2 fails because array must contain at most 2 items. "value" at position 3 fails because array must contain at most 2 items. "value" at position 4 fails because array must contain at most 2 items');
958
+ expect(err.details).to.have.length(4);
959
+ done();
960
+ });
961
+ });
962
+
963
+ it('errors when input has less items than ordered items', function (done) {
964
+
965
+ var schema = Joi.array().ordered([Joi.number().required(), Joi.string().required()]);
966
+ var input = [1];
967
+ schema.validate(input, function (err, value) {
968
+
969
+ expect(err).to.exist();
970
+ expect(err.message).to.equal('"value" does not contain 1 required value(s)');
971
+ done();
972
+ });
973
+ });
974
+
975
+ it('errors when input matches ordered items but not matches regular items', function (done) {
976
+
977
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number().required()]).items(Joi.number()).options({ abortEarly: false });
978
+ var input = ['s1', 2, 3, 4, 's5'];
979
+ schema.validate(input, function (err, value) {
980
+
981
+ expect(err).to.exist();
982
+ expect(err.message).to.equal('"value" at position 4 fails because ["4" must be a number]');
983
+ done();
984
+ });
985
+ });
986
+
987
+ it('errors when input does not match ordered items but matches regular items', function (done) {
988
+
989
+ var schema = Joi.array().ordered([Joi.string(), Joi.number()]).items(Joi.number()).options({ abortEarly: false });
990
+ var input = [1, 2, 3, 4, 5];
991
+ schema.validate(input, function (err, value) {
992
+
993
+ expect(err).to.exist();
994
+ expect(err.message).to.equal('"value" at position 0 fails because ["0" must be a string]');
995
+ done();
996
+ });
997
+ });
998
+
999
+ it('errors when input does not match ordered items not matches regular items', function (done) {
1000
+
1001
+ var schema = Joi.array().ordered([Joi.string(), Joi.number()]).items(Joi.string()).options({ abortEarly: false });
1002
+ var input = [1, 2, 3, 4, 5];
1003
+ schema.validate(input, function (err, value) {
1004
+
1005
+ expect(err).to.exist();
1006
+ expect(err.message).to.equal('"value" at position 0 fails because ["0" must be a string]. "value" at position 2 fails because ["2" must be a string]. "value" at position 3 fails because ["3" must be a string]. "value" at position 4 fails because ["4" must be a string]');
1007
+ expect(err.details).to.have.length(4);
1008
+ done();
1009
+ });
1010
+ });
1011
+
1012
+ it('errors but continues when abortEarly is set to false', function (done) {
1013
+
1014
+ var schema = Joi.array().ordered([Joi.number().required(), Joi.string().required()]).options({ abortEarly: false });
1015
+ var input = ['s1', 2];
1016
+ schema.validate(input, function (err, value) {
1017
+
1018
+ expect(err).to.exist();
1019
+ expect(err.message).to.equal('"value" at position 0 fails because ["0" must be a number]. "value" at position 1 fails because ["1" must be a string]');
1020
+ expect(err.details).to.have.length(2);
1021
+ done();
1022
+ });
1023
+ });
1024
+
1025
+ it('strips item', function (done) {
1026
+
1027
+ var schema = Joi.array().ordered([Joi.string().required(), Joi.number().strip(), Joi.number().required()]);
1028
+ var input = ['s1', 2, 3];
1029
+ schema.validate(input, function (err, value) {
1030
+
1031
+ expect(err).to.not.exist();
1032
+ expect(value).to.deep.equal(['s1', 3]);
1033
+ done();
1034
+ });
1035
+ });
1036
+
1037
+ it('strips multiple items', function (done) {
1038
+
1039
+ var schema = Joi.array().ordered([Joi.string().strip(), Joi.number(), Joi.number().strip()]);
1040
+ var input = ['s1', 2, 3];
1041
+ schema.validate(input, function (err, value) {
1042
+
1043
+ expect(err).to.not.exist();
1044
+ expect(value).to.deep.equal([2]);
1045
+ done();
1046
+ });
1047
+ });
1048
+ });
796
1049
  });
package/test/errors.js CHANGED
@@ -233,6 +233,41 @@ describe('errors', function () {
233
233
  });
234
234
  });
235
235
 
236
+ it('has a name that is ValidationError', function (done) {
237
+
238
+ var schema = Joi.number();
239
+ schema.validate('a', function (validateErr) {
240
+
241
+ expect(validateErr).to.exist();
242
+ expect(validateErr.name).to.be.equal('ValidationError');
243
+
244
+ try {
245
+ Joi.assert('a', schema);
246
+ throw new Error('should not reach that');
247
+ }
248
+ catch (assertErr) {
249
+ expect(assertErr.name).to.be.equal('ValidationError');
250
+ }
251
+
252
+ try {
253
+ Joi.assert('a', schema, 'foo');
254
+ throw new Error('should not reach that');
255
+ }
256
+ catch (assertErr) {
257
+ expect(assertErr.name).to.be.equal('ValidationError');
258
+ }
259
+
260
+ try {
261
+ Joi.assert('a', schema, new Error('foo'));
262
+ throw new Error('should not reach that');
263
+ }
264
+ catch (assertErr) {
265
+ expect(assertErr.name).to.equal('Error');
266
+ done();
267
+ }
268
+ });
269
+ });
270
+
236
271
  describe('#annotate', function () {
237
272
 
238
273
  it('annotates error', function (done) {
@@ -287,7 +322,7 @@ describe('errors', function () {
287
322
  it('annotates error within array multiple times on the same element', function (done) {
288
323
 
289
324
  var object = {
290
- a: [2, 3 , 4]
325
+ a: [2, 3, 4]
291
326
  };
292
327
 
293
328
  var schema = {
@@ -323,8 +358,8 @@ describe('errors', function () {
323
358
  it('annotates error within multiple arrays and multiple times on the same element', function (done) {
324
359
 
325
360
  var object = {
326
- a: [2, 3 , 4],
327
- b: [2, 3 , 4]
361
+ a: [2, 3, 4],
362
+ b: [2, 3, 4]
328
363
  };
329
364
 
330
365
  var schema = {
package/test/index.js CHANGED
@@ -1658,4 +1658,35 @@ describe('Joi', function () {
1658
1658
  done();
1659
1659
  });
1660
1660
  });
1661
+
1662
+ describe('#compile', function () {
1663
+
1664
+ it('throws an error on invalid value', function (done) {
1665
+
1666
+ expect(function () {
1667
+
1668
+ Joi.compile(undefined);
1669
+ }).to.throw(Error, 'Invalid schema content: ');
1670
+ done();
1671
+ });
1672
+
1673
+ it('shows path to errors in object', function (done) {
1674
+
1675
+ var schema = {
1676
+ a: {
1677
+ b: {
1678
+ c: {
1679
+ d: undefined
1680
+ }
1681
+ }
1682
+ }
1683
+ };
1684
+
1685
+ expect(function () {
1686
+
1687
+ Joi.compile(schema);
1688
+ }).to.throw(Error, 'Invalid schema content: (a.b.c.d)');
1689
+ done();
1690
+ });
1691
+ });
1661
1692
  });
package/test/object.js CHANGED
@@ -362,6 +362,17 @@ describe('object', function () {
362
362
  });
363
363
  });
364
364
 
365
+ it('errors on unknown nested keys with the correct path at the root level', function (done) {
366
+
367
+ var schema = Joi.object({ a: Joi.object().keys({}) });
368
+ var obj = { c: 'hello' };
369
+ schema.validate(obj, function (err, value) {
370
+
371
+ expect(err).to.exist();
372
+ expect(err.details[0].path).to.equal('c');
373
+ done();
374
+ });
375
+ });
365
376
 
366
377
  it('should work on prototype-less objects', function (done) {
367
378
 
@@ -881,6 +892,31 @@ describe('object', function () {
881
892
 
882
893
  describe('#pattern', function () {
883
894
 
895
+ it('shows path to errors in schema', function (done) {
896
+
897
+ expect(function () {
898
+
899
+ Joi.object().pattern(/.*/, {
900
+ a: {
901
+ b: {
902
+ c: {
903
+ d: undefined
904
+ }
905
+ }
906
+ }
907
+ });
908
+ }).to.throw(Error, 'Invalid schema content: (a.b.c.d)');
909
+
910
+ expect(function () {
911
+
912
+ Joi.object().pattern(/.*/, function () {
913
+
914
+ });
915
+ }).to.throw(Error, 'Invalid schema content: ');
916
+
917
+ done();
918
+ });
919
+
884
920
  it('validates unknown keys using a pattern', function (done) {
885
921
 
886
922
  var schema = Joi.object({
@@ -1081,6 +1117,32 @@ describe('object', function () {
1081
1117
 
1082
1118
  describe('#assert', function () {
1083
1119
 
1120
+ it('shows path to errors in schema', function (done) {
1121
+
1122
+ expect(function () {
1123
+
1124
+ Joi.object().assert('a.b', {
1125
+ a: {
1126
+ b: {
1127
+ c: {
1128
+ d: undefined
1129
+ }
1130
+ }
1131
+ }
1132
+ });
1133
+ }).to.throw(Error, 'Invalid schema content: (a.b.c.d)');
1134
+ done();
1135
+ });
1136
+
1137
+ it('shows errors in schema', function (done) {
1138
+
1139
+ expect(function () {
1140
+
1141
+ Joi.object().assert('a.b', undefined);
1142
+ }).to.throw(Error, 'Invalid schema content: ');
1143
+ done();
1144
+ });
1145
+
1084
1146
  it('validates upwards reference', function (done) {
1085
1147
 
1086
1148
  var schema = Joi.object({