Skip to content
This repository was archived by the owner on Dec 12, 2021. It is now read-only.

Commit c54cd8e

Browse files
committed
Add a type validator
This closes #80
1 parent 55d4e3a commit c54cd8e

File tree

3 files changed

+227
-3
lines changed

3 files changed

+227
-3
lines changed

index.html

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@
371371
<li><a href="#validators-length">Length</a></li>
372372
<li><a href="#validators-numericality">Numericality</a></li>
373373
<li><a href="#validators-presence">Presence</a></li>
374+
<li><a href="#validators-type">Type</a></li>
374375
<li><a href="#validators-url">URL</a></li>
375376
</ul>
376377
</li>
@@ -1778,6 +1779,51 @@ <h2>Validators</h2>
17781779
validate({}, {username: {presence: true}});
17791780
// =&gt; {"username": ["Username is required"]}
17801781
</code></pre>
1782+
</div>
1783+
<div id="validators-type">
1784+
<div class="signature"><b>type</b></div>
1785+
<p class="description">
1786+
The type validator ensures that the input is of the correct type. There are the following build in types.
1787+
</p>
1788+
<ul>
1789+
<li><code>array</code></li>
1790+
<li><code>integer</code></li>
1791+
<li><code>number</code></li>
1792+
<li><code>string</code></li>
1793+
<li><code>date</code></li>
1794+
<li><code>boolean</code></li>
1795+
</ul>
1796+
<p class="description">
1797+
In addition to these you can also create your own by adding them to
1798+
<code>validate.validator.type.types</code>.
1799+
</p>
1800+
<p class="description">
1801+
The following options are supported:
1802+
</p>
1803+
<ul>
1804+
<li>
1805+
<b>type</b> - The type to use. Can also be a function for inline type checking. The function will receive the value, options, attribute name, all attributes and the global options respectively.
1806+
</li>
1807+
<li>
1808+
<b>message</b> - A custom message. Can also be a function. The function will receive the value, options, attribute name, all attributes and the global options respectively.
1809+
</li>
1810+
</ul>
1811+
<pre><code class="javascript">validate({myAttribute: "value"}, {myAttribute: {type: "string"}});
1812+
// =&gt; undefined
1813+
1814+
validate({myAttribute: true}, {myAttribute: {type: "string"}});
1815+
// =&gt; {"myAttribute": ["My attribute must be of type string"]}
1816+
1817+
validate({myAttribute: "other"}, {myAttribute: {type: {type: function(value) { return value === "stuff"; }}}});
1818+
// =&gt; {"myAttribute": ["My attribute must be of the correct type"]}
1819+
1820+
validate.validators.type.types.customType = function (value) { return value === "stuff"; };
1821+
validate({myAttribute: true}, {myAttribute: {type: "customType"}});
1822+
// =&gt; {"myAttribute": ["My attribute must be of type customType"]}
1823+
1824+
validate.validators.type.messages.customType = "is simply wrong";
1825+
validate({myAttribute: true}, {myAttribute: {type: "customType"}});
1826+
// =&gt; {"myAttribute": ["My attribute is simply wrong"]}</code></pre>
17811827
</div>
17821828
<div id="validators-url">
17831829
<div class="signature"><b>url</b></div>
@@ -2376,6 +2422,11 @@ <h3>
23762422
<a href="https://github.com/ansman/validate.js/issues/215" target="_blank">Rhys Lloyd</a>
23772423
for reporting it.
23782424
</li>
2425+
<li>
2426+
There is now a type validator that will check the value's type. Thanks
2427+
<a href="https://github.com/ansman/validate.js/issues/80" target="_blank">Dmitry Kirilyuk</a>
2428+
for suggesting this.
2429+
</li>
23792430
</ul>
23802431
</div>
23812432
<div id="changelog-0-12-0">

specs/validators/type-spec.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
describe('validators.type', function() {
2+
var type = validate.validators.type;
3+
type = type.bind(type);
4+
5+
afterEach(function() {
6+
delete validate.validators.type.message;
7+
delete validate.validators.type.options;
8+
delete validate.validators.type.types.custom;
9+
});
10+
11+
it("allows empty values", function() {
12+
expect(type(null, "string", "foo", {})).not.toBeDefined();
13+
expect(type(undefined, "string", "foo", {})).not.toBeDefined();
14+
});
15+
16+
it("allows the correct type", function() {
17+
expect(type("", {type: "string"}, "foo", {})).not.toBeDefined();
18+
expect(type({}, {type: "object"}, "foo", {})).not.toBeDefined();
19+
expect(type([], {type: "array"}, "foo", {})).not.toBeDefined();
20+
expect(type(1, {type: "number"}, "foo", {})).not.toBeDefined();
21+
expect(type(1.1, {type: "number"}, "foo", {})).not.toBeDefined();
22+
expect(type(1, {type: "integer"}, "foo", {})).not.toBeDefined();
23+
expect(type(true, {type: "boolean"}, "foo", {})).not.toBeDefined();
24+
expect(type(new Date(), {type: "date"}, "foo", {})).not.toBeDefined();
25+
});
26+
27+
it("doesn't allow the incorrect type", function() {
28+
expect(type(new Date(), {type: "string"}, "foo", {})).toBeDefined();
29+
expect(type("", {type: "object"}, "foo", {})).toBeDefined();
30+
expect(type([], {type: "object"}, "foo", {})).toBeDefined();
31+
expect(type({}, {type: "array"}, "foo", {})).toBeDefined();
32+
expect(type([], {type: "number"}, "foo", {})).toBeDefined();
33+
expect(type(1.1, {type: "integer"}, "foo", {})).toBeDefined();
34+
expect(type(1, {type: "boolean"}, "foo", {})).toBeDefined();
35+
expect(type(true, {type: "date"}, "foo", {})).toBeDefined();
36+
});
37+
38+
it("has a nice default message", function() {
39+
expect(type(new Date(), {type: "string"}, "foo", {})).toBe("must be of type string");
40+
expect(type("", {type: "object"}, "foo", {})).toBe("must be of type object");
41+
expect(type({}, {type: "array"}, "foo", {})).toBe("must be of type array");
42+
expect(type([], {type: "number"}, "foo", {})).toBe("must be of type number");
43+
expect(type(1.1, {type: "integer"}, "foo", {})).toBe("must be of type integer");
44+
expect(type(1, {type: "boolean"}, "foo", {})).toBe("must be of type boolean");
45+
expect(type(true, {type: "date"}, "foo", {})).toBe("must be of type date");
46+
});
47+
48+
it("allows you to customize the error message", function() {
49+
validate.validators.type.message = "some message %{attribute}";
50+
expect(type("", {type: "object"}, "foo", {})).toBe("some message foo");
51+
var options = {type: "object", message: "some other message %{attribute}"};
52+
expect(type("", options, "foo", {})).toBe("some other message foo");
53+
});
54+
55+
it("allows functions as messages", function() {
56+
var message = function() { return "foo"; };
57+
var options = {type: "object", message: message};
58+
expect(type("", options, "foo", {})).toBe("foo");
59+
});
60+
61+
it("allows custom checks", function() {
62+
var globalOptions = {"globalOption": "globalValue"};
63+
var attributes = {"attr": "value"};
64+
var options = {type: "custom"};
65+
var ret = false;
66+
validate.validators.type.types.custom = function(value, opts, attr, attrs, gopts) {
67+
expect(value).toBe("value");
68+
expect(opts).toEqual(options);
69+
expect(attr).toBe("foo");
70+
expect(attrs).toBe(attributes);
71+
expect(gopts).toBe(globalOptions);
72+
return ret;
73+
};
74+
expect(type("value", options, "foo", attributes, globalOptions)).toEqual("must be of type custom");
75+
ret = true;
76+
expect(type("value", options, "foo", attributes, globalOptions)).not.toBeDefined();
77+
});
78+
79+
it("allows inline checks", function() {
80+
var globalOptions = {"globalOption": "globalValue"};
81+
var attributes = {"attr": "value"};
82+
var value = "value";
83+
var options = {
84+
type: function(v, opts, attr, attrs, gopts) {
85+
expect(v).toBe(value);
86+
expect(opts).toEqual(options);
87+
expect(attr).toBe("foo");
88+
expect(attrs).toBe(attributes);
89+
expect(gopts).toBe(globalOptions);
90+
return value === "other";
91+
}
92+
};
93+
expect(type(value, options, "foo", attributes, globalOptions)).toEqual("must be of the correct type");
94+
value = "other";
95+
expect(type(value, options, "foo", attributes, globalOptions)).not.toBeDefined();
96+
});
97+
98+
it("allows custom messages per check", function() {
99+
var globalOptions = {"globalOption": "globalValue"};
100+
var attributes = {"attr": "value"};
101+
var options = {type: "custom"};
102+
validate.validators.type.types.custom = function() { return false; };
103+
validate.validators.type.messages.custom = "my custom message";
104+
expect(type("value", options, "foo", globalOptions)).toEqual("my custom message");
105+
validate.validators.type.messages.custom = function(value, opts, attr, attrs, gopts) {
106+
expect(value).toBe("value");
107+
expect(opts).toEqual(options);
108+
expect(attr).toBe("foo");
109+
expect(attrs).toBe(attributes);
110+
expect(gopts).toBe(globalOptions);
111+
return "my other custom message";
112+
};
113+
expect(type("value", options, "foo", attributes, globalOptions)).toEqual("my other custom message");
114+
});
115+
116+
it("throws if the type isn't valid", function() {
117+
expect(function() { type("", {}, "foo", {}); }).toThrow();
118+
expect(function() { type("", "invalid", "foo", {}); }).toThrow();
119+
});
120+
});

validate.js

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@
566566
}
567567
}
568568
} else {
569-
var _val = typeof input.options[input.selectedIndex] !== 'undefined' ? input.options[input.selectedIndex].value : '';
569+
var _val = typeof input.options[input.selectedIndex] !== 'undefined' ? input.options[input.selectedIndex].value : /* istanbul ignore next */ '';
570570
value = v.sanitizeFormValue(_val, options);
571571
}
572572
values[input.name] = value;
@@ -1088,7 +1088,6 @@
10881088
return v.format(message, {attribute: prettify(options.attribute)});
10891089
}
10901090
},
1091-
10921091
// A URL validator that is used to validate URLs with the ability to
10931092
// restrict schemes and some domains.
10941093
url: function(value, options) {
@@ -1154,7 +1153,61 @@
11541153
if (!PATTERN.exec(value)) {
11551154
return message;
11561155
}
1157-
}
1156+
},
1157+
type: v.extend(function(value, originalOptions, attribute, attributes, globalOptions) {
1158+
if (v.isString(originalOptions)) {
1159+
originalOptions = {type: originalOptions};
1160+
}
1161+
1162+
if (!v.isDefined(value)) {
1163+
return;
1164+
}
1165+
1166+
var options = v.extend({}, this.options, originalOptions);
1167+
1168+
var type = options.type;
1169+
if (!v.isDefined(type)) {
1170+
throw new Error("No type was specified");
1171+
}
1172+
1173+
var check;
1174+
if (v.isFunction(type)) {
1175+
check = type;
1176+
} else {
1177+
check = this.types[type];
1178+
}
1179+
1180+
if (!v.isFunction(check)) {
1181+
throw new Error("validate.validators.type.types." + type + " must be a function.");
1182+
}
1183+
1184+
if (!check(value, options, attribute, attributes, globalOptions)) {
1185+
var message = originalOptions.message ||
1186+
this.messages[type] ||
1187+
this.message ||
1188+
options.message ||
1189+
(v.isFunction(type) ? "must be of the correct type" : "must be of type %{type}");
1190+
1191+
if (v.isFunction(message)) {
1192+
message = message(value, originalOptions, attribute, attributes, globalOptions);
1193+
}
1194+
1195+
return v.format(message, {attribute: v.prettify(attribute), type: type});
1196+
}
1197+
}, {
1198+
types: {
1199+
object: function(value) {
1200+
return v.isObject(value) && !v.isArray(value);
1201+
},
1202+
array: v.isArray,
1203+
integer: v.isInteger,
1204+
number: v.isNumber,
1205+
string: v.isString,
1206+
date: v.isDate,
1207+
boolean: v.isBoolean
1208+
},
1209+
messages: {}
1210+
})
11581211
};
11591212

11601213
validate.formatters = {

0 commit comments

Comments
 (0)