joi 4.6.2 → 4.9.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/.c9/.nakignore +17 -0
- package/.c9/metadata/tab0 +1 -0
- package/.c9/metadata/workspace/.c9/project.settings +1 -0
- package/.c9/metadata/workspace/.travis.yml +1 -0
- package/.c9/metadata/workspace/Makefile +1 -0
- package/.c9/metadata/workspace/README.md +1 -0
- package/.c9/metadata/workspace/lib/any.js +1 -0
- package/.c9/metadata/workspace/lib/array.js +1 -0
- package/.c9/metadata/workspace/lib/binary.js +1 -0
- package/.c9/metadata/workspace/lib/boolean.js +1 -0
- package/.c9/metadata/workspace/lib/cast.js +1 -0
- package/.c9/metadata/workspace/lib/date.js +1 -0
- package/.c9/metadata/workspace/lib/errors.js +1 -0
- package/.c9/metadata/workspace/lib/function.js +1 -0
- package/.c9/metadata/workspace/lib/index.js +1 -0
- package/.c9/metadata/workspace/lib/language.js +1 -0
- package/.c9/metadata/workspace/lib/number.js +1 -0
- package/.c9/metadata/workspace/lib/object.js +1 -0
- package/.c9/metadata/workspace/lib/string.js +1 -0
- package/.c9/metadata/workspace/package.json +1 -0
- package/.c9/metadata/workspace/test/alternatives.js +1 -0
- package/.c9/metadata/workspace/test/any.js +1 -0
- package/.c9/metadata/workspace/test/array.js +1 -0
- package/.c9/metadata/workspace/test/binary.js +1 -0
- package/.c9/metadata/workspace/test/boolean.js +1 -0
- package/.c9/metadata/workspace/test/date.js +1 -0
- package/.c9/metadata/workspace/test/errors.js +1 -0
- package/.c9/metadata/workspace/test/function.js +1 -0
- package/.c9/metadata/workspace/test/helper.js +1 -0
- package/.c9/metadata/workspace/test/index.js +1 -0
- package/.c9/metadata/workspace/test/number.js +1 -0
- package/.c9/metadata/workspace/test/object.js +1 -0
- package/.c9/metadata/workspace/test/ref.js +1 -0
- package/.c9/metadata/workspace/test/string.js +1 -0
- package/.c9/project.settings +34 -0
- package/.travis.yml +1 -0
- package/Makefile +3 -3
- package/README.md +162 -31
- package/examples/conditionalRequire.js +43 -0
- package/examples/customMessage.js +22 -0
- package/examples/multipleWhen.js +17 -0
- package/lib/any.js +35 -24
- package/lib/array.js +25 -0
- package/lib/date.js +64 -9
- package/lib/errors.js +18 -2
- package/lib/index.js +3 -2
- package/lib/language.js +17 -6
- package/lib/number.js +50 -0
- package/lib/object.js +69 -5
- package/lib/string.js +27 -6
- package/package.json +5 -3
- package/test/alternatives.js +12 -10
- package/test/any.js +64 -15
- package/test/array.js +55 -16
- package/test/binary.js +13 -11
- package/test/boolean.js +9 -7
- package/test/date.js +143 -20
- package/test/errors.js +26 -15
- package/test/function.js +7 -5
- package/test/helper.js +7 -5
- package/test/index.js +116 -73
- package/test/number.js +125 -12
- package/test/object.js +192 -43
- package/test/ref.js +22 -20
- package/test/string.js +255 -106
- package/AUTHORS +0 -3
package/lib/date.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
var Any = require('./any');
|
|
4
4
|
var Errors = require('./errors');
|
|
5
5
|
var Hoek = require('hoek');
|
|
6
|
+
var Moment = require('moment');
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
// Declare internals
|
|
9
10
|
|
|
10
11
|
var internals = {};
|
|
11
12
|
|
|
13
|
+
internals.isoDate = /^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\d))$/;
|
|
14
|
+
internals.invalidDate = new Date('');
|
|
12
15
|
|
|
13
16
|
internals.Date = function () {
|
|
14
17
|
|
|
@@ -22,15 +25,21 @@ Hoek.inherits(internals.Date, Any);
|
|
|
22
25
|
internals.Date.prototype._base = function (value, state, options) {
|
|
23
26
|
|
|
24
27
|
var result = {
|
|
25
|
-
value: (options.convert && internals.toDate(value)) || value
|
|
28
|
+
value: (options.convert && internals.toDate(value, this._flags.format)) || value
|
|
26
29
|
};
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
if (result.value instanceof Date && !isNaN(result.value.getTime())) {
|
|
32
|
+
result.errors = null;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
result.errors = Errors.create(this._flags.format === internals.isoDate ? 'date.isoDate' : 'date.base', null, state, options);
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
return result;
|
|
30
39
|
};
|
|
31
40
|
|
|
32
41
|
|
|
33
|
-
internals.toDate = function (value) {
|
|
42
|
+
internals.toDate = function (value, format) {
|
|
34
43
|
|
|
35
44
|
if (value instanceof Date) {
|
|
36
45
|
return value;
|
|
@@ -40,12 +49,25 @@ internals.toDate = function (value) {
|
|
|
40
49
|
Hoek.isInteger(value)) {
|
|
41
50
|
|
|
42
51
|
if (typeof value === 'string' &&
|
|
43
|
-
|
|
52
|
+
/^[+-]?\d+$/.test(value)) {
|
|
44
53
|
|
|
45
54
|
value = parseInt(value, 10);
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
var date
|
|
57
|
+
var date;
|
|
58
|
+
if (format) {
|
|
59
|
+
if (format === internals.isoDate) {
|
|
60
|
+
date = format.test(value) ? new Date(value) : internals.invalidDate;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
date = Moment(value, format, true);
|
|
64
|
+
date = date.isValid() ? date.toDate() : internals.invalidDate;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
date = new Date(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
if (!isNaN(date.getTime())) {
|
|
50
72
|
return date;
|
|
51
73
|
}
|
|
@@ -57,12 +79,17 @@ internals.toDate = function (value) {
|
|
|
57
79
|
|
|
58
80
|
internals.Date.prototype.min = function (date) {
|
|
59
81
|
|
|
60
|
-
|
|
82
|
+
var isNow = date === 'now';
|
|
83
|
+
|
|
84
|
+
if (!isNow) {
|
|
85
|
+
date = internals.toDate(date);
|
|
86
|
+
}
|
|
87
|
+
|
|
61
88
|
Hoek.assert(date, 'Invalid date format');
|
|
62
89
|
|
|
63
90
|
return this._test('min', date, function (value, state, options) {
|
|
64
91
|
|
|
65
|
-
if (value.getTime() >= date.getTime()) {
|
|
92
|
+
if (value.getTime() >= (isNow ? Date.now() : date.getTime())) {
|
|
66
93
|
return null;
|
|
67
94
|
}
|
|
68
95
|
|
|
@@ -73,12 +100,17 @@ internals.Date.prototype.min = function (date) {
|
|
|
73
100
|
|
|
74
101
|
internals.Date.prototype.max = function (date) {
|
|
75
102
|
|
|
76
|
-
|
|
103
|
+
var isNow = date === 'now';
|
|
104
|
+
|
|
105
|
+
if (!isNow) {
|
|
106
|
+
date = internals.toDate(date);
|
|
107
|
+
}
|
|
108
|
+
|
|
77
109
|
Hoek.assert(date, 'Invalid date format');
|
|
78
110
|
|
|
79
111
|
return this._test('max', date, function (value, state, options) {
|
|
80
112
|
|
|
81
|
-
if (value.getTime() <= date.getTime()) {
|
|
113
|
+
if (value.getTime() <= (isNow ? Date.now() : date.getTime())) {
|
|
82
114
|
return null;
|
|
83
115
|
}
|
|
84
116
|
|
|
@@ -86,5 +118,28 @@ internals.Date.prototype.max = function (date) {
|
|
|
86
118
|
});
|
|
87
119
|
};
|
|
88
120
|
|
|
121
|
+
internals.Date.prototype.format = function (format) {
|
|
122
|
+
|
|
123
|
+
Hoek.assert(typeof format === 'string' || (Array.isArray(format) && format.every(function (f) {
|
|
124
|
+
|
|
125
|
+
return typeof f === 'string';
|
|
126
|
+
})), 'Invalid format.');
|
|
127
|
+
|
|
128
|
+
var obj = this.clone();
|
|
129
|
+
obj._flags.format = format;
|
|
130
|
+
return obj;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
internals.Date.prototype.iso = function () {
|
|
134
|
+
|
|
135
|
+
var obj = this.clone();
|
|
136
|
+
obj._flags.format = internals.isoDate;
|
|
137
|
+
return obj;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
internals.Date.prototype._isIsoDate = function (value) {
|
|
141
|
+
|
|
142
|
+
return internals.isoDate.test(value);
|
|
143
|
+
};
|
|
89
144
|
|
|
90
145
|
module.exports = new internals.Date();
|
package/lib/errors.js
CHANGED
|
@@ -24,7 +24,7 @@ internals.Err.prototype.toString = function () {
|
|
|
24
24
|
var self = this;
|
|
25
25
|
|
|
26
26
|
var localized = this.options.language;
|
|
27
|
-
this.context.key = this.context.key || localized.root || Language.errors.root;
|
|
27
|
+
this.context.key = localized.label || this.context.key || localized.root || Language.errors.root;
|
|
28
28
|
|
|
29
29
|
var format = Hoek.reach(localized, this.type) || Hoek.reach(Language.errors, this.type);
|
|
30
30
|
var hasKey = /\{\{\!?key\}\}/.test(format);
|
|
@@ -57,7 +57,7 @@ exports.process = function (errors, object) {
|
|
|
57
57
|
var item = errors[i];
|
|
58
58
|
details.push({
|
|
59
59
|
message: item.toString(),
|
|
60
|
-
path:
|
|
60
|
+
path: internals.getPath(item),
|
|
61
61
|
type: item.type
|
|
62
62
|
});
|
|
63
63
|
}
|
|
@@ -79,6 +79,22 @@ exports.process = function (errors, object) {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
internals.getPath = function (item) {
|
|
83
|
+
|
|
84
|
+
var recursePath = function (it) {
|
|
85
|
+
|
|
86
|
+
var reachedItem = Hoek.reach(it, 'context.reason.0');
|
|
87
|
+
if (reachedItem && reachedItem.context) {
|
|
88
|
+
return recursePath(reachedItem);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return it.path;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return recursePath(item) || item.context.key;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
|
|
82
98
|
internals.annotate = function () {
|
|
83
99
|
|
|
84
100
|
var obj = Hoek.clone(this._object || {});
|
package/lib/index.js
CHANGED
|
@@ -113,11 +113,12 @@ internals.root = function () {
|
|
|
113
113
|
return Cast.schema(schema);
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
root.assert = function (value, schema) {
|
|
116
|
+
root.assert = function (value, schema, message) {
|
|
117
117
|
|
|
118
|
+
message = (message ? message + ' ' : '');
|
|
118
119
|
var error = root.validate(value, schema).error;
|
|
119
120
|
if (error) {
|
|
120
|
-
throw new Error(error.annotate());
|
|
121
|
+
throw new Error(message + error.annotate());
|
|
121
122
|
}
|
|
122
123
|
};
|
|
123
124
|
|
package/lib/language.js
CHANGED
|
@@ -25,7 +25,8 @@ exports.errors = {
|
|
|
25
25
|
excludes: 'position {{pos}} contains an excluded value',
|
|
26
26
|
min: 'must contain at least {{limit}} items',
|
|
27
27
|
max: 'must contain less than or equal to {{limit}} items',
|
|
28
|
-
length: 'must contain {{limit}} items'
|
|
28
|
+
length: 'must contain {{limit}} items',
|
|
29
|
+
unique: 'position {{pos}} contains a duplicate value'
|
|
29
30
|
},
|
|
30
31
|
boolean: {
|
|
31
32
|
base: 'must be a boolean'
|
|
@@ -39,7 +40,8 @@ exports.errors = {
|
|
|
39
40
|
date: {
|
|
40
41
|
base: 'must be a number of milliseconds or valid date string',
|
|
41
42
|
min: 'must be larger than or equal to {{limit}}',
|
|
42
|
-
max: 'must be less than or equal to {{limit}}'
|
|
43
|
+
max: 'must be less than or equal to {{limit}}',
|
|
44
|
+
isoDate: 'must be a valid ISO 8601 date'
|
|
43
45
|
},
|
|
44
46
|
function: {
|
|
45
47
|
base: 'must be a Function'
|
|
@@ -56,20 +58,25 @@ exports.errors = {
|
|
|
56
58
|
xor: 'contains a conflict between exclusive peers {{peers}}',
|
|
57
59
|
or: 'must contain at least one of {{peers}}',
|
|
58
60
|
and: 'contains {{present}} without its required peers {{missing}}',
|
|
61
|
+
nand: '{{main}} must not exist simultaneously with {{peers}}',
|
|
59
62
|
assert: 'validation failed because {{ref}} failed to {{message}}',
|
|
60
63
|
rename: {
|
|
61
64
|
multiple: 'cannot rename child {{from}} because multiple renames are disabled and another key was already renamed to {{to}}',
|
|
62
65
|
override: 'cannot rename child {{from}} because override is disabled and target {{to}} exists'
|
|
63
|
-
}
|
|
66
|
+
},
|
|
67
|
+
type: 'must be an instance of {{type}}'
|
|
64
68
|
},
|
|
65
69
|
number: {
|
|
66
70
|
base: 'must be a number',
|
|
67
71
|
min: 'must be larger than or equal to {{limit}}',
|
|
68
72
|
max: 'must be less than or equal to {{limit}}',
|
|
73
|
+
less: 'must be less than {{limit}}',
|
|
74
|
+
greater: 'must be greater than {{limit}}',
|
|
69
75
|
float: 'must be a float or double',
|
|
70
76
|
integer: 'must be an integer',
|
|
71
77
|
negative: 'must be a negative number',
|
|
72
|
-
positive: 'must be a positive number'
|
|
78
|
+
positive: 'must be a positive number',
|
|
79
|
+
precision: 'must have no more than {{limit}} decimal places'
|
|
73
80
|
},
|
|
74
81
|
string: {
|
|
75
82
|
base: 'must be a string',
|
|
@@ -78,13 +85,17 @@ exports.errors = {
|
|
|
78
85
|
length: 'length must be {{limit}} characters long',
|
|
79
86
|
alphanum: 'must only contain alpha-numeric characters',
|
|
80
87
|
token: 'must only contain alpha-numeric and underscore characters',
|
|
81
|
-
regex:
|
|
88
|
+
regex: {
|
|
89
|
+
base: 'fails to match the required pattern',
|
|
90
|
+
name: 'fails to match the {{name}} pattern'
|
|
91
|
+
},
|
|
82
92
|
email: 'must be a valid email',
|
|
83
93
|
isoDate: 'must be a valid ISO 8601 date',
|
|
84
94
|
guid: 'must be a valid GUID',
|
|
85
95
|
hostname: 'must be a valid hostname',
|
|
86
96
|
lowercase: 'must only contain lowercase characters',
|
|
87
97
|
uppercase: 'must only contain uppercase characters',
|
|
88
|
-
trim: 'must not have leading or trailing whitespace'
|
|
98
|
+
trim: 'must not have leading or trailing whitespace',
|
|
99
|
+
creditCard: 'must be a credit card'
|
|
89
100
|
}
|
|
90
101
|
};
|
package/lib/number.js
CHANGED
|
@@ -68,6 +68,36 @@ internals.Number.prototype.max = function (limit) {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
internals.Number.prototype.greater = function (limit) {
|
|
72
|
+
|
|
73
|
+
Hoek.assert(Hoek.isInteger(limit), 'limit must be an integer');
|
|
74
|
+
|
|
75
|
+
return this._test('greater', limit, function (value, state, options) {
|
|
76
|
+
|
|
77
|
+
if (value > limit) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Errors.create('number.greater', { limit: limit }, state, options);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
internals.Number.prototype.less = function (limit) {
|
|
87
|
+
|
|
88
|
+
Hoek.assert(Hoek.isInteger(limit), 'limit must be an integer');
|
|
89
|
+
|
|
90
|
+
return this._test('less', limit, function (value, state, options) {
|
|
91
|
+
|
|
92
|
+
if (value < limit) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Errors.create('number.less', { limit: limit }, state, options);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
|
|
71
101
|
internals.Number.prototype.integer = function () {
|
|
72
102
|
|
|
73
103
|
return this._test('integer', undefined, function (value, state, options) {
|
|
@@ -103,4 +133,24 @@ internals.Number.prototype.positive = function () {
|
|
|
103
133
|
};
|
|
104
134
|
|
|
105
135
|
|
|
136
|
+
internals.precisionRx = /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/;
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
internals.Number.prototype.precision = function (limit) {
|
|
140
|
+
|
|
141
|
+
Hoek.assert(Hoek.isInteger(limit), 'limit must be an integer');
|
|
142
|
+
|
|
143
|
+
return this._test('precision', limit, function (value, state, options){
|
|
144
|
+
|
|
145
|
+
var places = value.toString().match(internals.precisionRx);
|
|
146
|
+
var decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0);
|
|
147
|
+
if (decimals <= limit) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return Errors.create('number.precision', {limit: limit}, state, options);
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
|
|
106
156
|
module.exports = new internals.Number();
|
package/lib/object.js
CHANGED
|
@@ -55,11 +55,21 @@ internals.Object.prototype._base = function (value, state, options) {
|
|
|
55
55
|
return finish();
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// Skip if there are no other rules to test
|
|
59
|
+
|
|
60
|
+
if (!this._inner.renames.length &&
|
|
61
|
+
!this._inner.dependencies.length &&
|
|
62
|
+
!this._inner.children && // null allows any keys
|
|
63
|
+
!this._inner.patterns.length) {
|
|
64
|
+
|
|
65
|
+
target = value;
|
|
66
|
+
return finish();
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
// Ensure target is a local copy (parsed) or shallow copy
|
|
59
70
|
|
|
60
71
|
if (target === value) {
|
|
61
|
-
target =
|
|
62
|
-
target.__proto__ = Object.getPrototypeOf(value);
|
|
72
|
+
target = Object.create(Object.getPrototypeOf(value));
|
|
63
73
|
var valueKeys = Object.keys(value);
|
|
64
74
|
for (var t = 0, tl = valueKeys.length; t < tl; ++t) {
|
|
65
75
|
target[valueKeys[t]] = value[valueKeys[t]];
|
|
@@ -320,13 +330,15 @@ internals.Object.prototype.max = function (limit) {
|
|
|
320
330
|
};
|
|
321
331
|
|
|
322
332
|
|
|
323
|
-
internals.Object.prototype.pattern = function (
|
|
333
|
+
internals.Object.prototype.pattern = function (pattern, schema) {
|
|
324
334
|
|
|
325
|
-
Hoek.assert(
|
|
335
|
+
Hoek.assert(pattern instanceof RegExp, 'Invalid regular expression');
|
|
326
336
|
Hoek.assert(schema !== undefined, 'Invalid rule');
|
|
327
337
|
|
|
338
|
+
pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
|
|
339
|
+
|
|
328
340
|
var obj = this.clone();
|
|
329
|
-
obj._inner.patterns.push({ regex:
|
|
341
|
+
obj._inner.patterns.push({ regex: pattern, rule: Cast.schema(schema) });
|
|
330
342
|
return obj;
|
|
331
343
|
};
|
|
332
344
|
|
|
@@ -364,6 +376,13 @@ internals.Object.prototype.and = function () {
|
|
|
364
376
|
};
|
|
365
377
|
|
|
366
378
|
|
|
379
|
+
internals.Object.prototype.nand = function () {
|
|
380
|
+
|
|
381
|
+
var peers = Hoek.flatten(Array.prototype.slice.call(arguments));
|
|
382
|
+
return this._dependency('nand', null, peers);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
|
|
367
386
|
internals.renameDefaults = {
|
|
368
387
|
alias: false, // Keep old value in place
|
|
369
388
|
multiple: false, // Allow renaming multiple keys into the same target
|
|
@@ -504,6 +523,25 @@ internals.and = function (value, peers, parent, state, options) {
|
|
|
504
523
|
};
|
|
505
524
|
|
|
506
525
|
|
|
526
|
+
internals.nand = function (value, peers, parent, state, options) {
|
|
527
|
+
|
|
528
|
+
var present = [];
|
|
529
|
+
for (var i = 0, il = peers.length; i < il; ++i) {
|
|
530
|
+
var peer = peers[i];
|
|
531
|
+
if (parent.hasOwnProperty(peer) &&
|
|
532
|
+
parent[peer] !== undefined) {
|
|
533
|
+
|
|
534
|
+
present.push(peer);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
var values = Hoek.clone(peers);
|
|
539
|
+
var main = values.splice(0,1);
|
|
540
|
+
var allPresent = (present.length === peers.length);
|
|
541
|
+
return allPresent ? Errors.create('object.nand', { main: main, peers: values }, state, options) : null;
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
|
|
507
545
|
internals.Object.prototype.describe = function (shallow) {
|
|
508
546
|
|
|
509
547
|
var description = Any.prototype.describe.call(this);
|
|
@@ -522,6 +560,15 @@ internals.Object.prototype.describe = function (shallow) {
|
|
|
522
560
|
description.dependencies = Hoek.clone(this._inner.dependencies);
|
|
523
561
|
}
|
|
524
562
|
|
|
563
|
+
if (this._inner.patterns.length) {
|
|
564
|
+
description.patterns = [];
|
|
565
|
+
|
|
566
|
+
for (var p = 0, pl = this._inner.patterns.length; p < pl; ++p) {
|
|
567
|
+
var pattern = this._inner.patterns[p];
|
|
568
|
+
description.patterns.push({ regex: pattern.regex.toString(), rule: pattern.rule.describe() });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
525
572
|
return description;
|
|
526
573
|
};
|
|
527
574
|
|
|
@@ -530,6 +577,7 @@ internals.Object.prototype.assert = function (ref, schema, message) {
|
|
|
530
577
|
|
|
531
578
|
ref = Cast.ref(ref);
|
|
532
579
|
Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead');
|
|
580
|
+
message = message || 'pass the assertion test';
|
|
533
581
|
|
|
534
582
|
var cast = Cast.schema(schema);
|
|
535
583
|
|
|
@@ -545,4 +593,20 @@ internals.Object.prototype.assert = function (ref, schema, message) {
|
|
|
545
593
|
};
|
|
546
594
|
|
|
547
595
|
|
|
596
|
+
internals.Object.prototype.type = function (constructor, name) {
|
|
597
|
+
|
|
598
|
+
Hoek.assert(typeof constructor === 'function', 'type must be a constructor function');
|
|
599
|
+
name = name || constructor.name;
|
|
600
|
+
|
|
601
|
+
return this._test('type', name, function (value, state, options) {
|
|
602
|
+
|
|
603
|
+
if (value instanceof constructor) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return Errors.create('object.type', { type: name }, state, options);
|
|
608
|
+
});
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
|
|
548
612
|
module.exports = new internals.Object();
|
package/lib/string.js
CHANGED
|
@@ -4,9 +4,9 @@ var Net = require('net');
|
|
|
4
4
|
var Hoek = require('hoek');
|
|
5
5
|
var Isemail = require('isemail');
|
|
6
6
|
var Any = require('./any');
|
|
7
|
+
var JoiDate = require('./date');
|
|
7
8
|
var Errors = require('./errors');
|
|
8
9
|
|
|
9
|
-
|
|
10
10
|
// Declare internals
|
|
11
11
|
|
|
12
12
|
var internals = {};
|
|
@@ -85,6 +85,27 @@ internals.String.prototype.max = function (limit, encoding) {
|
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
internals.String.prototype.creditCard = function () {
|
|
89
|
+
|
|
90
|
+
return this._test('creditCard', undefined, function (value, state, options) {
|
|
91
|
+
|
|
92
|
+
var i = value.length;
|
|
93
|
+
var sum = 0;
|
|
94
|
+
var mul = 1;
|
|
95
|
+
var char;
|
|
96
|
+
|
|
97
|
+
while (i--) {
|
|
98
|
+
char = value.charAt(i) * mul;
|
|
99
|
+
sum += char - (char > 9) * 9;
|
|
100
|
+
mul ^= 3;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
var check = (sum % 10 === 0) && (sum > 0);
|
|
104
|
+
return check ? null : Errors.create('string.creditCard', null, state, options);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
|
|
88
109
|
internals.String.prototype.length = function (limit, encoding) {
|
|
89
110
|
|
|
90
111
|
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer');
|
|
@@ -102,17 +123,19 @@ internals.String.prototype.length = function (limit, encoding) {
|
|
|
102
123
|
};
|
|
103
124
|
|
|
104
125
|
|
|
105
|
-
internals.String.prototype.regex = function (pattern) {
|
|
126
|
+
internals.String.prototype.regex = function (pattern, name) {
|
|
106
127
|
|
|
107
128
|
Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp');
|
|
108
129
|
|
|
130
|
+
pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
|
|
131
|
+
|
|
109
132
|
return this._test('regex', pattern, function (value, state, options) {
|
|
110
133
|
|
|
111
134
|
if (pattern.test(value)) {
|
|
112
135
|
return null;
|
|
113
136
|
}
|
|
114
137
|
|
|
115
|
-
return Errors.create('string.regex',
|
|
138
|
+
return Errors.create((name ? 'string.regex.name' : 'string.regex.base'), { name: name }, state, options);
|
|
116
139
|
});
|
|
117
140
|
};
|
|
118
141
|
|
|
@@ -158,11 +181,9 @@ internals.String.prototype.email = function () {
|
|
|
158
181
|
|
|
159
182
|
internals.String.prototype.isoDate = function () {
|
|
160
183
|
|
|
161
|
-
var regex = /^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))$/;
|
|
162
|
-
|
|
163
184
|
return this._test('isoDate', undefined, function (value, state, options) {
|
|
164
185
|
|
|
165
|
-
if (
|
|
186
|
+
if (JoiDate._isIsoDate(value)) {
|
|
166
187
|
return null;
|
|
167
188
|
}
|
|
168
189
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joi",
|
|
3
3
|
"description": "Object schema validation",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.9.0",
|
|
5
5
|
"repository": "git://github.com/hapijs/joi",
|
|
6
6
|
"main": "index",
|
|
7
7
|
"keywords": [
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"hoek": "^2.2.x",
|
|
16
16
|
"topo": "1.x.x",
|
|
17
|
-
"isemail": "1.x.x"
|
|
17
|
+
"isemail": "1.x.x",
|
|
18
|
+
"moment": "2.x.x"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"
|
|
21
|
+
"code": "1.x.x",
|
|
22
|
+
"lab": "5.x.x"
|
|
21
23
|
},
|
|
22
24
|
"scripts": {
|
|
23
25
|
"test": "make test-cov"
|
package/test/alternatives.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Load modules
|
|
2
2
|
|
|
3
3
|
var Lab = require('lab');
|
|
4
|
+
var Code = require('code');
|
|
4
5
|
var Joi = require('..');
|
|
5
6
|
var Helper = require('./helper');
|
|
6
7
|
|
|
@@ -12,11 +13,12 @@ var internals = {};
|
|
|
12
13
|
|
|
13
14
|
// Test shortcuts
|
|
14
15
|
|
|
15
|
-
var
|
|
16
|
-
var before =
|
|
17
|
-
var after =
|
|
18
|
-
var describe =
|
|
19
|
-
var it =
|
|
16
|
+
var lab = exports.lab = Lab.script();
|
|
17
|
+
var before = lab.before;
|
|
18
|
+
var after = lab.after;
|
|
19
|
+
var describe = lab.describe;
|
|
20
|
+
var it = lab.it;
|
|
21
|
+
var expect = Code.expect;
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
describe('alternatives', function () {
|
|
@@ -25,7 +27,7 @@ describe('alternatives', function () {
|
|
|
25
27
|
|
|
26
28
|
Joi.alternatives().validate('a', function (err, value) {
|
|
27
29
|
|
|
28
|
-
expect(err).to.exist;
|
|
30
|
+
expect(err).to.exist();
|
|
29
31
|
expect(err.message).to.equal('value not matching any of the allowed alternatives');
|
|
30
32
|
done();
|
|
31
33
|
});
|
|
@@ -35,7 +37,7 @@ describe('alternatives', function () {
|
|
|
35
37
|
|
|
36
38
|
Joi.alternatives().validate(undefined, function (err, value) {
|
|
37
39
|
|
|
38
|
-
expect(err).to.not.exist;
|
|
40
|
+
expect(err).to.not.exist();
|
|
39
41
|
done();
|
|
40
42
|
});
|
|
41
43
|
});
|
|
@@ -51,7 +53,7 @@ describe('alternatives', function () {
|
|
|
51
53
|
|
|
52
54
|
schema.validate({ a: '5' }, function (err, value) {
|
|
53
55
|
|
|
54
|
-
expect(err).to.not.exist;
|
|
56
|
+
expect(err).to.not.exist();
|
|
55
57
|
expect(value.a).to.equal(5);
|
|
56
58
|
done();
|
|
57
59
|
});
|
|
@@ -68,7 +70,7 @@ describe('alternatives', function () {
|
|
|
68
70
|
|
|
69
71
|
schema.validate({ a: '5' }, function (err, value) {
|
|
70
72
|
|
|
71
|
-
expect(err).to.not.exist;
|
|
73
|
+
expect(err).to.not.exist();
|
|
72
74
|
expect(value.a).to.equal(5);
|
|
73
75
|
done();
|
|
74
76
|
});
|
|
@@ -86,7 +88,7 @@ describe('alternatives', function () {
|
|
|
86
88
|
var input = { a: { b: 'any', d: 'string' } };
|
|
87
89
|
schema.validate(input, function (err, value) {
|
|
88
90
|
|
|
89
|
-
expect(err).to.not.exist;
|
|
91
|
+
expect(err).to.not.exist();
|
|
90
92
|
expect(value.a.b).to.equal('any');
|
|
91
93
|
done();
|
|
92
94
|
});
|