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 +14 -3
- package/README.md +9 -1
- package/lib/any.js +1 -8
- package/lib/index.js +25 -0
- package/lib/string/rfc3986.js +12 -1
- package/lib/string/uri.js +9 -2
- package/lib/string.js +7 -2
- package/package.json +1 -1
- package/test/any.js +34 -22
- package/test/index.js +67 -0
- package/test/string.js +856 -761
package/API.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- version -->
|
|
2
|
-
# 7.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
[](http://travis-ci.org/hapijs/joi)
|
|
7
7
|
[](https://david-dm.org/hapijs/joi)
|
|
8
8
|
[](https://david-dm.org/hapijs/joi#info=devDependencies)
|
|
9
|
+
[](https://snyk.io/test/npm/joi)
|
|
9
10
|
|
|
10
11
|
[](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.
|
|
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
|
-
|
|
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/string/rfc3986.js
CHANGED
|
@@ -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('^' +
|
|
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
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
|
});
|