joi 7.2.3 → 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.3 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)
@@ -223,6 +224,15 @@ const ref = Joi.ref('a');
223
224
  Joi.isRef(ref); // returns true
224
225
  ```
225
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
+
226
236
  ### `any`
227
237
 
228
238
  Generates a schema object that matches any data type.
@@ -502,7 +512,7 @@ Considers anything that matches the schema to be empty (`undefined`).
502
512
  - `schema` - any object or joi schema to match. An undefined schema unsets that rule.
503
513
 
504
514
  ```js
505
- const schema = Joi.string().empty('');
515
+ let schema = Joi.string().empty('');
506
516
  schema.validate(''); // returns { error: null, value: undefined }
507
517
  schema = schema.empty();
508
518
  schema.validate(''); // returns { error: "value" is not allowed to be empty, value: '' }
@@ -524,7 +534,7 @@ array.validate(['a', 'b', 'a'], (err, value) => { });
524
534
  Allow this array to be sparse. `enabled` can be used with a falsy value to go back to the default behavior.
525
535
 
526
536
  ```javascript
527
- const schema = Joi.array().sparse(); // undefined values are now allowed
537
+ let schema = Joi.array().sparse(); // undefined values are now allowed
528
538
  schema = schema.sparse(false); // undefined values are now denied
529
539
  ```
530
540
 
@@ -1388,6 +1398,7 @@ Requires the string value to be a valid [RFC 3986](http://tools.ietf.org/html/rf
1388
1398
 
1389
1399
  - `options` - optional settings:
1390
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`.
1391
1402
 
1392
1403
  ```javascript
1393
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](https://github.com/hapijs/joi/blob/v7.2.3/API.md).
98
+ See the [API Reference](https://github.com/hapijs/joi/blob/v7.3.0/API.md).
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
 
@@ -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,7 @@
1
1
  {
2
2
  "name": "joi",
3
3
  "description": "Object schema validation",
4
- "version": "7.2.3",
4
+ "version": "7.3.0",
5
5
  "homepage": "https://github.com/hapijs/joi",
6
6
  "repository": "git://github.com/hapijs/joi",
7
7
  "main": "lib/index.js",
package/test/any.js CHANGED
@@ -1503,27 +1503,6 @@ describe('any', () => {
1503
1503
 
1504
1504
  describe('Set', () => {
1505
1505
 
1506
- describe('add()', () => {
1507
-
1508
- it('throws when adding a non ref function', (done) => {
1509
-
1510
- expect(() => {
1511
-
1512
- Joi.valid(() => { });
1513
- }).to.throw('Value cannot be an object or function');
1514
- done();
1515
- });
1516
-
1517
- it('throws when adding an object function', (done) => {
1518
-
1519
- expect(() => {
1520
-
1521
- Joi.valid({});
1522
- }).to.throw('Value cannot be an object or function');
1523
- done();
1524
- });
1525
- });
1526
-
1527
1506
  describe('has()', () => {
1528
1507
 
1529
1508
  it('compares date to null', (done) => {
@@ -1541,6 +1520,7 @@ describe('any', () => {
1541
1520
  expect(any._valids.has(new Buffer(''))).to.equal(false);
1542
1521
  done();
1543
1522
  });
1523
+
1544
1524
  });
1545
1525
 
1546
1526
  describe('values()', () => {
@@ -1592,7 +1572,7 @@ describe('any', () => {
1592
1572
 
1593
1573
  expect(() => {
1594
1574
 
1595
- Joi.any().valid(true, 1, 'hello', new Date());
1575
+ Joi.any().valid(true, 1, 'hello', new Date(), Symbol('foo'), () => {}, {});
1596
1576
  }).not.to.throw();
1597
1577
  done();
1598
1578
  });
@@ -1605,6 +1585,38 @@ describe('any', () => {
1605
1585
  }).to.throw(Error, 'Cannot call allow/valid/invalid with undefined');
1606
1586
  done();
1607
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
+ });
1608
1620
  });
1609
1621
 
1610
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
  });