joi 7.2.0 → 7.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/API.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <!-- version -->
2
- # 7.2.0 API Reference
2
+ # 7.3.0 API Reference
3
3
  <!-- versionstop -->
4
4
 
5
5
  <img src="https://raw.github.com/hapijs/joi/master/images/validation.png" align="right" />
@@ -12,6 +12,7 @@
12
12
  - [`assert(value, schema, [message])`](#assertvalue-schema-message)
13
13
  - [`attempt(value, schema, [message])`](#attemptvalue-schema-message)
14
14
  - [`isRef(ref)`](#isrefref)
15
+ - [`reach(schema, path)`](#reachschema-path)
15
16
  - [`any`](#any)
16
17
  - [`any.allow(value)`](#anyallowvalue)
17
18
  - [`any.valid(value)` - aliases: `only`, `equal`](#anyvalidvalue---aliases-only-equal)
@@ -131,7 +132,11 @@ Validates a value using the given schema and options where:
131
132
  - `allowUnknown` - when `true`, allows object to contain unknown keys which are ignored. Defaults to `false`.
132
133
  - `skipFunctions` - when `true`, ignores unknown keys with a function value. Defaults to `false`.
133
134
  - `stripUnknown` - when `true`, unknown keys are deleted (only when value is an object or an array). Defaults to `false`.
134
- - `language` - overrides individual error messages, when `'label'` is set, it overrides the key name in the error message. Defaults to no override (`{}`).
135
+ - `language` - overrides individual error messages. Defaults to no override (`{}`). Messages apply the following rules :
136
+ - variables are put between curly braces like `{{var}}`, if prefixed by a `!` like `{{!var}}`, it will be html escaped
137
+ - strings are always preceeded by the key name, unless a `{{key}}` is found elsewhere or if the string is prefixed by a `!!`
138
+ - when `'label'` is set, it overrides the key name in the error message
139
+ - to better understand the structure of the language, it's advised to have a look at the existing messages you want to override [here](lib/language.js)
135
140
  - `presence` - sets the default presence requirements. Supported modes: `'optional'`, `'required'`, and `'forbidden'`.
136
141
  Defaults to `'optional'`.
137
142
  - `context` - provides an external data set to be used in [references](#refkey-options). Can only be set as an external option to
@@ -219,6 +224,15 @@ const ref = Joi.ref('a');
219
224
  Joi.isRef(ref); // returns true
220
225
  ```
221
226
 
227
+ ### `reach(schema, path)`
228
+
229
+ Get a sub-schema of an existing schema based on a path. Path separatar is a dot (`.`).
230
+
231
+ ```js
232
+ const schema = Joi.object({ foo: Joi.object({ bar: Joi.number() }});
233
+ const number = Joi.reach(schema, 'foo.bar');
234
+ ```
235
+
222
236
  ### `any`
223
237
 
224
238
  Generates a schema object that matches any data type.
@@ -498,7 +512,7 @@ Considers anything that matches the schema to be empty (`undefined`).
498
512
  - `schema` - any object or joi schema to match. An undefined schema unsets that rule.
499
513
 
500
514
  ```js
501
- const schema = Joi.string().empty('');
515
+ let schema = Joi.string().empty('');
502
516
  schema.validate(''); // returns { error: null, value: undefined }
503
517
  schema = schema.empty();
504
518
  schema.validate(''); // returns { error: "value" is not allowed to be empty, value: '' }
@@ -520,7 +534,7 @@ array.validate(['a', 'b', 'a'], (err, value) => { });
520
534
  Allow this array to be sparse. `enabled` can be used with a falsy value to go back to the default behavior.
521
535
 
522
536
  ```javascript
523
- const schema = Joi.array().sparse(); // undefined values are now allowed
537
+ let schema = Joi.array().sparse(); // undefined values are now allowed
524
538
  schema = schema.sparse(false); // undefined values are now denied
525
539
  ```
526
540
 
@@ -1260,7 +1274,7 @@ const schema = Joi.object({
1260
1274
 
1261
1275
  Specifies the maximum number of string characters where:
1262
1276
  - `limit` - the maximum number of string characters allowed.
1263
- - `encoding` - is specified, the string length is calculated in bytes using the provided encoding.
1277
+ - `encoding` - if specified, the string length is calculated in bytes using the provided encoding.
1264
1278
 
1265
1279
  ```javascript
1266
1280
  const schema = Joi.string().max(10);
@@ -1384,6 +1398,7 @@ Requires the string value to be a valid [RFC 3986](http://tools.ietf.org/html/rf
1384
1398
 
1385
1399
  - `options` - optional settings:
1386
1400
  - `scheme` - Specifies one or more acceptable Schemes, should only include the scheme name. Can be an Array or String (strings are automatically escaped for use in a Regular Expression).
1401
+ - `allowRelative` - Allow relative URIs. Defaults to `false`.
1387
1402
 
1388
1403
  ```javascript
1389
1404
  // Accept git or git http/https
package/README.md CHANGED
@@ -6,11 +6,19 @@ Object schema description language and validator for JavaScript objects.
6
6
  [![Build Status](https://secure.travis-ci.org/hapijs/joi.svg)](http://travis-ci.org/hapijs/joi)
7
7
  [![Dependencies Status](https://david-dm.org/hapijs/joi.svg)](https://david-dm.org/hapijs/joi)
8
8
  [![DevDependencies Status](https://david-dm.org/hapijs/joi/dev-status.svg)](https://david-dm.org/hapijs/joi#info=devDependencies)
9
+ [![Known Vulnerabilities](https://snyk.io/test/npm/joi/badge.svg)](https://snyk.io/test/npm/joi)
9
10
 
10
11
  [![Join the chat at https://gitter.im/hapijs/joi](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hapijs/joi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
11
12
 
12
13
  Lead Maintainer: [Nicolas Morel](https://github.com/marsup)
13
14
 
15
+ # Introduction
16
+
17
+ Imagine you run facebook and you want visitors to sign up on the website with real names and not something like `l337_p@nda` in the first name field. How would you define the limitations of what can be inputted and validate it against the set rules?
18
+
19
+ 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.
20
+
21
+
14
22
  # Example
15
23
 
16
24
  ```javascript
@@ -87,4 +95,4 @@ When validating a schema:
87
95
  * Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks.
88
96
 
89
97
  # API
90
- See the [API Reference](API.md).
98
+ See the [API Reference](https://github.com/hapijs/joi/blob/v7.3.0/API.md).
@@ -9,13 +9,18 @@ const Package = require('./package.json');
9
9
  // Declare internals
10
10
 
11
11
  const internals = {
12
- filename: './API.md'
12
+ api: {
13
+ filename: './API.md',
14
+ contents: Fs.readFileSync('./API.md', 'utf8')
15
+ },
16
+ readme: {
17
+ filename: './README.md',
18
+ contents: Fs.readFileSync('./README.md', 'utf8')
19
+ }
13
20
  };
14
21
 
22
+ internals.generateToc = function () {
15
23
 
16
- internals.generate = function () {
17
-
18
- const api = Fs.readFileSync(internals.filename, 'utf8');
19
24
  const tocOptions = {
20
25
  bullets: '-',
21
26
  slugify: function (text) {
@@ -26,10 +31,19 @@ internals.generate = function () {
26
31
  }
27
32
  };
28
33
 
29
- const output = Toc.insert(api, tocOptions)
30
- .replace(/<!-- version -->(.|\n)*<!-- versionstop -->/, '<!-- version -->\n# ' + Package.version + ' API Reference\n<!-- versionstop -->');
34
+ const api = Toc.insert(internals.api.contents, tocOptions)
35
+ .replace(/<!-- version -->(.|\n)*<!-- versionstop -->/, `<!-- version -->\n# ${Package.version} API Reference\n<!-- versionstop -->`);
36
+
37
+ Fs.writeFileSync(internals.api.filename, api);
38
+ };
39
+
40
+ internals.generateLink = function () {
41
+ // create absolute URL for versioned docs
42
+ const readme = internals.readme.contents
43
+ .replace(/\[API Reference\]\(.*\)/gi, `[API Reference](${Package.homepage || ''}/blob/v${Package.version}/${internals.api.filename.substr(2)})`);
31
44
 
32
- Fs.writeFileSync(internals.filename, output);
45
+ Fs.writeFileSync(internals.readme.filename, readme);
33
46
  };
34
47
 
35
- internals.generate();
48
+ internals.generateToc();
49
+ internals.generateLink();
package/lib/any.js CHANGED
@@ -219,8 +219,6 @@ internals.Any.prototype.concat = function (schema) {
219
219
 
220
220
  internals.Any.prototype._test = function (name, arg, func) {
221
221
 
222
- Hoek.assert(!this._flags.allowOnly, 'Cannot define rules when valid values specified');
223
-
224
222
  const obj = this.clone();
225
223
  obj._tests.push({ func, name, arg });
226
224
  return obj;
@@ -279,8 +277,6 @@ internals.Any.prototype.allow = function () {
279
277
 
280
278
  internals.Any.prototype.valid = internals.Any.prototype.only = internals.Any.prototype.equal = function () {
281
279
 
282
- Hoek.assert(!this._tests.length, 'Cannot set valid values when rules specified');
283
-
284
280
  const obj = this.allow.apply(this, arguments);
285
281
  obj._flags.allowOnly = true;
286
282
  return obj;
@@ -769,10 +765,7 @@ internals.Set = function () {
769
765
 
770
766
  internals.Set.prototype.add = function (value, refs) {
771
767
 
772
- Hoek.assert(value === null || value === undefined || value instanceof Date || Buffer.isBuffer(value) || Ref.isRef(value) || (typeof value !== 'function' && typeof value !== 'object'), 'Value cannot be an object or function');
773
-
774
- if (typeof value !== 'function' &&
775
- this.has(value, null, null, false)) {
768
+ if (!Ref.isRef(value) && this.has(value, null, null, false)) {
776
769
 
777
770
  return;
778
771
  }
package/lib/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // Load modules
4
4
 
5
+ const Hoek = require('hoek');
5
6
  const Any = require('./any');
6
7
  const Cast = require('./cast');
7
8
  const Ref = require('./ref');
@@ -147,6 +148,30 @@ internals.root = function () {
147
148
  return result.value;
148
149
  };
149
150
 
151
+ root.reach = function (schema, path) {
152
+
153
+ Hoek.assert(schema && schema.isJoi, 'you must provide a joi schema');
154
+ Hoek.assert(typeof path === 'string', 'path must be a string');
155
+
156
+ if (path === '') {
157
+ return schema;
158
+ }
159
+
160
+ const parts = path.split('.');
161
+ const children = schema._inner.children;
162
+ if (!children) {
163
+ return;
164
+ }
165
+
166
+ const key = parts[0];
167
+ for (let i = 0; i < children.length; ++i) {
168
+ const child = children[i];
169
+ if (child.key === key) {
170
+ return this.reach(child.schema, path.substr(key.length + 1));
171
+ }
172
+ }
173
+ };
174
+
150
175
  return root;
151
176
  };
152
177
 
package/lib/object.js CHANGED
@@ -145,6 +145,11 @@ internals.Object.prototype._base = function (value, state, options) {
145
145
 
146
146
  const unprocessed = Hoek.mapToObject(Object.keys(target));
147
147
 
148
+ // Children mustn't inherit the current label if it exists
149
+ const childOptions = options.language && options.language.label ?
150
+ Hoek.applyToDefaults(options, { language: { label: null } }, true) :
151
+ options;
152
+
148
153
  if (this._inner.children) {
149
154
  for (let i = 0; i < this._inner.children.length; ++i) {
150
155
  const child = this._inner.children[i];
@@ -154,9 +159,9 @@ internals.Object.prototype._base = function (value, state, options) {
154
159
  delete unprocessed[key];
155
160
 
156
161
  const localState = { key, path: (state.path || '') + (state.path && key ? '.' : '') + key, parent: target, reference: state.reference };
157
- const result = child.schema._validate(item, localState, options);
162
+ const result = child.schema._validate(item, localState, childOptions);
158
163
  if (result.errors) {
159
- errors.push(this.createError('object.child', { key, reason: result.errors }, localState, options));
164
+ errors.push(this.createError('object.child', { key, reason: result.errors }, localState, childOptions));
160
165
 
161
166
  if (options.abortEarly) {
162
167
  return finish();
@@ -231,7 +236,7 @@ internals.Object.prototype._base = function (value, state, options) {
231
236
  (this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) {
232
237
 
233
238
  for (let i = 0; i < unprocessedKeys.length; ++i) {
234
- errors.push(this.createError('object.allowUnknown', null, { key: unprocessedKeys[i], path: state.path + (state.path ? '.' : '') + unprocessedKeys[i] }, options));
239
+ errors.push(this.createError('object.allowUnknown', null, { key: unprocessedKeys[i], path: state.path + (state.path ? '.' : '') + unprocessedKeys[i] }, childOptions));
235
240
  }
236
241
  }
237
242
  }
@@ -162,14 +162,25 @@ internals.generate = function () {
162
162
  */
163
163
  const segment = pcharOnly + '*';
164
164
  const segmentNz = pcharOnly + '+';
165
+ const segmentNzNc = '[' + unreserved + pctEncoded + subDelims + '@' + ']+';
166
+ const pathEmpty = '';
165
167
  const pathAbEmpty = '(?:\\/' + segment + ')*';
166
168
  const pathAbsolute = '\\/(?:' + segmentNz + pathAbEmpty + ')?';
167
169
  const pathRootless = segmentNz + pathAbEmpty;
170
+ const pathNoScheme = segmentNzNc + pathAbEmpty;
168
171
 
169
172
  /**
170
173
  * hier-part = "//" authority path
171
174
  */
172
- internals.rfc3986.hierPart = '(?:\\/\\/' + authority + pathAbEmpty + or + pathAbsolute + or + pathRootless + ')';
175
+ internals.rfc3986.hierPart = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathRootless + ')';
176
+
177
+ /**
178
+ * relative-part = "//" authority path-abempty
179
+ * / path-absolute
180
+ * / path-noscheme
181
+ * / path-empty
182
+ */
183
+ internals.rfc3986.relativeRef = '(?:' + '(?:\\/\\/' + authority + pathAbEmpty + ')' + or + pathAbsolute + or + pathNoScheme + or + pathEmpty + ')';
173
184
 
174
185
  /**
175
186
  * query = *( pchar / "/" / "?" )
package/lib/string/uri.js CHANGED
@@ -9,7 +9,7 @@ const RFC3986 = require('./rfc3986');
9
9
 
10
10
  const internals = {
11
11
  Uri: {
12
- createUriRegex: function (optionalScheme) {
12
+ createUriRegex: function (optionalScheme, allowRelative) {
13
13
 
14
14
  let scheme = RFC3986.scheme;
15
15
 
@@ -20,10 +20,17 @@ const internals = {
20
20
  scheme = '(?:' + optionalScheme + ')';
21
21
  }
22
22
 
23
+ const withScheme = '(?:' + scheme + ':' + RFC3986.hierPart + ')';
24
+ const prefix = allowRelative ? '(?:' + withScheme + '|' + RFC3986.relativeRef + ')' : withScheme;
25
+
23
26
  /**
24
27
  * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
28
+ *
29
+ * OR
30
+ *
31
+ * relative-ref = relative-part [ "?" query ] [ "#" fragment ]
25
32
  */
26
- return new RegExp('^' + scheme + ':' + RFC3986.hierPart + '(?:\\?' + RFC3986.query + ')?' + '(?:#' + RFC3986.fragment + ')?$');
33
+ return new RegExp('^' + prefix + '(?:\\?' + RFC3986.query + ')?' + '(?:#' + RFC3986.fragment + ')?$');
27
34
  }
28
35
  }
29
36
  };
package/lib/string.js CHANGED
@@ -273,6 +273,7 @@ internals.String.prototype.ip = function (ipOptions) {
273
273
  internals.String.prototype.uri = function (uriOptions) {
274
274
 
275
275
  let customScheme = '';
276
+ let allowRelative = false;
276
277
  let regex = internals.uriRegex;
277
278
 
278
279
  if (uriOptions) {
@@ -305,10 +306,14 @@ internals.String.prototype.uri = function (uriOptions) {
305
306
  }
306
307
  }
307
308
  }
309
+
310
+ if (uriOptions.allowRelative) {
311
+ allowRelative = true;
312
+ }
308
313
  }
309
314
 
310
- if (customScheme) {
311
- regex = Uri.createUriRegex(customScheme);
315
+ if (customScheme || allowRelative) {
316
+ regex = Uri.createUriRegex(customScheme, allowRelative);
312
317
  }
313
318
 
314
319
  return this._test('uri', uriOptions, (value, state, options) => {
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "7.2.0",
4
+ "version": "7.3.0",
5
+ "homepage": "https://github.com/hapijs/joi",
5
6
  "repository": "git://github.com/hapijs/joi",
6
7
  "main": "lib/index.js",
7
8
  "keywords": [
@@ -27,7 +28,7 @@
27
28
  "test": "lab -t 100 -a code -L",
28
29
  "test-cov-html": "lab -r html -o coverage.html -a code",
29
30
  "toc": "node generate-readme-toc.js",
30
- "version": "npm run toc && git add API.md"
31
+ "version": "npm run toc && git add API.md README.md"
31
32
  },
32
33
  "license": "BSD-3-Clause"
33
34
  }
package/test/any.js CHANGED
@@ -171,6 +171,39 @@ describe('any', () => {
171
171
  expect(schema).to.deep.equal({ type: 'object', label: 'lbl' });
172
172
  done();
173
173
  });
174
+
175
+ it('does not leak into sub objects', (done) => {
176
+
177
+ const schema = Joi.object({ a: Joi.number() }).label('foo');
178
+ schema.validate({ a: 'a' }, (err, value) => {
179
+
180
+ expect(err).to.exist();
181
+ expect(err.message).to.equal('child "a" fails because ["a" must be a number]');
182
+ done();
183
+ });
184
+ });
185
+
186
+ it('does not leak into sub objects from an array', (done) => {
187
+
188
+ const schema = Joi.array().items(Joi.object({ a: Joi.number() }).label('foo')).label('bar');
189
+ schema.validate([{ a: 'a' }], (err, value) => {
190
+
191
+ expect(err).to.exist();
192
+ expect(err.message).to.equal('"bar" at position 0 fails because [child "a" fails because ["a" must be a number]]');
193
+ done();
194
+ });
195
+ });
196
+
197
+ it('does not leak into unknown keys', (done) => {
198
+
199
+ const schema = Joi.object({ a: Joi.number() }).label('foo');
200
+ schema.validate({ b: 'a' }, (err, value) => {
201
+
202
+ expect(err).to.exist();
203
+ expect(err.message).to.equal('"b" is not allowed');
204
+ done();
205
+ });
206
+ });
174
207
  });
175
208
 
176
209
  describe('strict()', () => {
@@ -1470,27 +1503,6 @@ describe('any', () => {
1470
1503
 
1471
1504
  describe('Set', () => {
1472
1505
 
1473
- describe('add()', () => {
1474
-
1475
- it('throws when adding a non ref function', (done) => {
1476
-
1477
- expect(() => {
1478
-
1479
- Joi.valid(() => { });
1480
- }).to.throw('Value cannot be an object or function');
1481
- done();
1482
- });
1483
-
1484
- it('throws when adding an object function', (done) => {
1485
-
1486
- expect(() => {
1487
-
1488
- Joi.valid({});
1489
- }).to.throw('Value cannot be an object or function');
1490
- done();
1491
- });
1492
- });
1493
-
1494
1506
  describe('has()', () => {
1495
1507
 
1496
1508
  it('compares date to null', (done) => {
@@ -1508,6 +1520,7 @@ describe('any', () => {
1508
1520
  expect(any._valids.has(new Buffer(''))).to.equal(false);
1509
1521
  done();
1510
1522
  });
1523
+
1511
1524
  });
1512
1525
 
1513
1526
  describe('values()', () => {
@@ -1559,7 +1572,7 @@ describe('any', () => {
1559
1572
 
1560
1573
  expect(() => {
1561
1574
 
1562
- Joi.any().valid(true, 1, 'hello', new Date());
1575
+ Joi.any().valid(true, 1, 'hello', new Date(), Symbol('foo'), () => {}, {});
1563
1576
  }).not.to.throw();
1564
1577
  done();
1565
1578
  });
@@ -1572,6 +1585,38 @@ describe('any', () => {
1572
1585
  }).to.throw(Error, 'Cannot call allow/valid/invalid with undefined');
1573
1586
  done();
1574
1587
  });
1588
+
1589
+ it('validates differents types of values', (done) => {
1590
+
1591
+ expect(Joi.valid(1).validate(1).error).to.be.null();
1592
+ expect(Joi.valid(1).validate(2).error).to.exist();
1593
+
1594
+ const d = new Date();
1595
+ expect(Joi.valid(d).validate(new Date(d.getTime())).error).to.be.null();
1596
+ expect(Joi.valid(d).validate(new Date(d.getTime() + 1)).error).to.exist();
1597
+
1598
+ const str = 'foo';
1599
+ expect(Joi.valid(str).validate(str).error).to.be.null();
1600
+ expect(Joi.valid(str).validate('foobar').error).to.exist();
1601
+
1602
+ const s = Symbol('foo');
1603
+ expect(Joi.valid(s).validate(s).error).to.be.null();
1604
+ expect(Joi.valid(s).validate(Symbol('foo')).error).to.exist();
1605
+
1606
+ const o = {};
1607
+ expect(Joi.valid(o).validate(o).error).to.be.null();
1608
+ expect(Joi.valid(o).validate({}).error).to.exist();
1609
+
1610
+ const f = () => {};
1611
+ expect(Joi.valid(f).validate(f).error).to.be.null();
1612
+ expect(Joi.valid(f).validate(() => {}).error).to.exist();
1613
+
1614
+ const b = new Buffer('foo');
1615
+ expect(Joi.valid(b).validate(b).error).to.be.null();
1616
+ expect(Joi.valid(b).validate(new Buffer('foobar')).error).to.exist();
1617
+
1618
+ done();
1619
+ });
1575
1620
  });
1576
1621
 
1577
1622
  describe('invalid()', () => {
package/test/index.js CHANGED
@@ -1691,4 +1691,71 @@ describe('Joi', () => {
1691
1691
  done();
1692
1692
  });
1693
1693
  });
1694
+
1695
+ describe('reach()', () => {
1696
+
1697
+ it('should fail without any parameter', (done) => {
1698
+
1699
+ expect(() => Joi.reach()).to.throw('you must provide a joi schema');
1700
+ done();
1701
+ });
1702
+
1703
+ it('should fail when schema is not a joi object', (done) => {
1704
+
1705
+ expect(() => Joi.reach({ foo: 'bar' }, 'foo')).to.throw('you must provide a joi schema');
1706
+ done();
1707
+ });
1708
+
1709
+ it('should fail without a proper path', (done) => {
1710
+
1711
+ const schema = Joi.object();
1712
+ expect(() => Joi.reach(schema)).to.throw('path must be a string');
1713
+ expect(() => Joi.reach(schema, true)).to.throw('path must be a string');
1714
+ done();
1715
+ });
1716
+
1717
+ it('should return undefined when no keys are defined', (done) => {
1718
+
1719
+ const schema = Joi.object();
1720
+ expect(Joi.reach(schema, 'a')).to.be.undefined();
1721
+ done();
1722
+ });
1723
+
1724
+ it('should return undefined when key is not found', (done) => {
1725
+
1726
+ const schema = Joi.object().keys({ a: Joi.number() });
1727
+ expect(Joi.reach(schema, 'foo')).to.be.undefined();
1728
+ done();
1729
+ });
1730
+
1731
+ it('should return a schema when key is found', (done) => {
1732
+
1733
+ const a = Joi.number();
1734
+ const schema = Joi.object().keys({ a });
1735
+ expect(Joi.reach(schema, 'a')).to.equal(a);
1736
+ done();
1737
+ });
1738
+
1739
+ it('should return undefined on a schema that does not support reach', (done) => {
1740
+
1741
+ const schema = Joi.number();
1742
+ expect(Joi.reach(schema, 'a')).to.be.undefined();
1743
+ done();
1744
+ });
1745
+
1746
+ it('should return a schema when deep key is found', (done) => {
1747
+
1748
+ const bar = Joi.number();
1749
+ const schema = Joi.object({ foo: Joi.object({ bar }) });
1750
+ expect(Joi.reach(schema, 'foo.bar')).to.equal(bar);
1751
+ done();
1752
+ });
1753
+
1754
+ it('should return undefined when deep key is not found', (done) => {
1755
+
1756
+ const schema = Joi.object({ foo: Joi.object({ bar: Joi.number() }) });
1757
+ expect(Joi.reach(schema, 'foo.baz')).to.be.undefined();
1758
+ done();
1759
+ });
1760
+ });
1694
1761
  });