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

Commit

Permalink
Add a type validator
Browse files Browse the repository at this point in the history
This closes #80
  • Loading branch information
ansman committed Nov 22, 2017
1 parent 55d4e3a commit c54cd8e
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 3 deletions.
51 changes: 51 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@
<li><a href="#validators-length">Length</a></li>
<li><a href="#validators-numericality">Numericality</a></li>
<li><a href="#validators-presence">Presence</a></li>
<li><a href="#validators-type">Type</a></li>
<li><a href="#validators-url">URL</a></li>
</ul>
</li>
Expand Down Expand Up @@ -1778,6 +1779,51 @@ <h2>Validators</h2>
validate({}, {username: {presence: true}});
// =&gt; {"username": ["Username is required"]}
</code></pre>
</div>
<div id="validators-type">
<div class="signature"><b>type</b></div>
<p class="description">
The type validator ensures that the input is of the correct type. There are the following build in types.
</p>
<ul>
<li><code>array</code></li>
<li><code>integer</code></li>
<li><code>number</code></li>
<li><code>string</code></li>
<li><code>date</code></li>
<li><code>boolean</code></li>
</ul>
<p class="description">
In addition to these you can also create your own by adding them to
<code>validate.validator.type.types</code>.
</p>
<p class="description">
The following options are supported:
</p>
<ul>
<li>
<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.
</li>
<li>
<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.
</li>
</ul>
<pre><code class="javascript">validate({myAttribute: "value"}, {myAttribute: {type: "string"}});
// =&gt; undefined

validate({myAttribute: true}, {myAttribute: {type: "string"}});
// =&gt; {"myAttribute": ["My attribute must be of type string"]}

validate({myAttribute: "other"}, {myAttribute: {type: {type: function(value) { return value === "stuff"; }}}});
// =&gt; {"myAttribute": ["My attribute must be of the correct type"]}

validate.validators.type.types.customType = function (value) { return value === "stuff"; };
validate({myAttribute: true}, {myAttribute: {type: "customType"}});
// =&gt; {"myAttribute": ["My attribute must be of type customType"]}

validate.validators.type.messages.customType = "is simply wrong";
validate({myAttribute: true}, {myAttribute: {type: "customType"}});
// =&gt; {"myAttribute": ["My attribute is simply wrong"]}</code></pre>
</div>
<div id="validators-url">
<div class="signature"><b>url</b></div>
Expand Down Expand Up @@ -2376,6 +2422,11 @@ <h3>
<a href="https://github.com/ansman/validate.js/issues/215" target="_blank">Rhys Lloyd</a>
for reporting it.
</li>
<li>
There is now a type validator that will check the value's type. Thanks
<a href="https://github.com/ansman/validate.js/issues/80" target="_blank">Dmitry Kirilyuk</a>
for suggesting this.
</li>
</ul>
</div>
<div id="changelog-0-12-0">
Expand Down
120 changes: 120 additions & 0 deletions specs/validators/type-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
describe('validators.type', function() {
var type = validate.validators.type;
type = type.bind(type);

afterEach(function() {
delete validate.validators.type.message;
delete validate.validators.type.options;
delete validate.validators.type.types.custom;
});

it("allows empty values", function() {
expect(type(null, "string", "foo", {})).not.toBeDefined();
expect(type(undefined, "string", "foo", {})).not.toBeDefined();
});

it("allows the correct type", function() {
expect(type("", {type: "string"}, "foo", {})).not.toBeDefined();
expect(type({}, {type: "object"}, "foo", {})).not.toBeDefined();
expect(type([], {type: "array"}, "foo", {})).not.toBeDefined();
expect(type(1, {type: "number"}, "foo", {})).not.toBeDefined();
expect(type(1.1, {type: "number"}, "foo", {})).not.toBeDefined();
expect(type(1, {type: "integer"}, "foo", {})).not.toBeDefined();
expect(type(true, {type: "boolean"}, "foo", {})).not.toBeDefined();
expect(type(new Date(), {type: "date"}, "foo", {})).not.toBeDefined();
});

it("doesn't allow the incorrect type", function() {
expect(type(new Date(), {type: "string"}, "foo", {})).toBeDefined();
expect(type("", {type: "object"}, "foo", {})).toBeDefined();
expect(type([], {type: "object"}, "foo", {})).toBeDefined();
expect(type({}, {type: "array"}, "foo", {})).toBeDefined();
expect(type([], {type: "number"}, "foo", {})).toBeDefined();
expect(type(1.1, {type: "integer"}, "foo", {})).toBeDefined();
expect(type(1, {type: "boolean"}, "foo", {})).toBeDefined();
expect(type(true, {type: "date"}, "foo", {})).toBeDefined();
});

it("has a nice default message", function() {
expect(type(new Date(), {type: "string"}, "foo", {})).toBe("must be of type string");
expect(type("", {type: "object"}, "foo", {})).toBe("must be of type object");
expect(type({}, {type: "array"}, "foo", {})).toBe("must be of type array");
expect(type([], {type: "number"}, "foo", {})).toBe("must be of type number");
expect(type(1.1, {type: "integer"}, "foo", {})).toBe("must be of type integer");
expect(type(1, {type: "boolean"}, "foo", {})).toBe("must be of type boolean");
expect(type(true, {type: "date"}, "foo", {})).toBe("must be of type date");
});

it("allows you to customize the error message", function() {
validate.validators.type.message = "some message %{attribute}";
expect(type("", {type: "object"}, "foo", {})).toBe("some message foo");
var options = {type: "object", message: "some other message %{attribute}"};
expect(type("", options, "foo", {})).toBe("some other message foo");
});

it("allows functions as messages", function() {
var message = function() { return "foo"; };
var options = {type: "object", message: message};
expect(type("", options, "foo", {})).toBe("foo");
});

it("allows custom checks", function() {
var globalOptions = {"globalOption": "globalValue"};
var attributes = {"attr": "value"};
var options = {type: "custom"};
var ret = false;
validate.validators.type.types.custom = function(value, opts, attr, attrs, gopts) {
expect(value).toBe("value");
expect(opts).toEqual(options);
expect(attr).toBe("foo");
expect(attrs).toBe(attributes);
expect(gopts).toBe(globalOptions);
return ret;
};
expect(type("value", options, "foo", attributes, globalOptions)).toEqual("must be of type custom");
ret = true;
expect(type("value", options, "foo", attributes, globalOptions)).not.toBeDefined();
});

it("allows inline checks", function() {
var globalOptions = {"globalOption": "globalValue"};
var attributes = {"attr": "value"};
var value = "value";
var options = {
type: function(v, opts, attr, attrs, gopts) {
expect(v).toBe(value);
expect(opts).toEqual(options);
expect(attr).toBe("foo");
expect(attrs).toBe(attributes);
expect(gopts).toBe(globalOptions);
return value === "other";
}
};
expect(type(value, options, "foo", attributes, globalOptions)).toEqual("must be of the correct type");
value = "other";
expect(type(value, options, "foo", attributes, globalOptions)).not.toBeDefined();
});

it("allows custom messages per check", function() {
var globalOptions = {"globalOption": "globalValue"};
var attributes = {"attr": "value"};
var options = {type: "custom"};
validate.validators.type.types.custom = function() { return false; };
validate.validators.type.messages.custom = "my custom message";
expect(type("value", options, "foo", globalOptions)).toEqual("my custom message");
validate.validators.type.messages.custom = function(value, opts, attr, attrs, gopts) {
expect(value).toBe("value");
expect(opts).toEqual(options);
expect(attr).toBe("foo");
expect(attrs).toBe(attributes);
expect(gopts).toBe(globalOptions);
return "my other custom message";
};
expect(type("value", options, "foo", attributes, globalOptions)).toEqual("my other custom message");
});

it("throws if the type isn't valid", function() {
expect(function() { type("", {}, "foo", {}); }).toThrow();
expect(function() { type("", "invalid", "foo", {}); }).toThrow();
});
});
59 changes: 56 additions & 3 deletions validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@
}
}
} else {
var _val = typeof input.options[input.selectedIndex] !== 'undefined' ? input.options[input.selectedIndex].value : '';
var _val = typeof input.options[input.selectedIndex] !== 'undefined' ? input.options[input.selectedIndex].value : /* istanbul ignore next */ '';
value = v.sanitizeFormValue(_val, options);
}
values[input.name] = value;
Expand Down Expand Up @@ -1088,7 +1088,6 @@
return v.format(message, {attribute: prettify(options.attribute)});
}
},

// A URL validator that is used to validate URLs with the ability to
// restrict schemes and some domains.
url: function(value, options) {
Expand Down Expand Up @@ -1154,7 +1153,61 @@
if (!PATTERN.exec(value)) {
return message;
}
}
},
type: v.extend(function(value, originalOptions, attribute, attributes, globalOptions) {
if (v.isString(originalOptions)) {
originalOptions = {type: originalOptions};
}

if (!v.isDefined(value)) {
return;
}

var options = v.extend({}, this.options, originalOptions);

var type = options.type;
if (!v.isDefined(type)) {
throw new Error("No type was specified");
}

var check;
if (v.isFunction(type)) {
check = type;
} else {
check = this.types[type];
}

if (!v.isFunction(check)) {
throw new Error("validate.validators.type.types." + type + " must be a function.");
}

if (!check(value, options, attribute, attributes, globalOptions)) {
var message = originalOptions.message ||
this.messages[type] ||
this.message ||
options.message ||
(v.isFunction(type) ? "must be of the correct type" : "must be of type %{type}");

if (v.isFunction(message)) {
message = message(value, originalOptions, attribute, attributes, globalOptions);
}

return v.format(message, {attribute: v.prettify(attribute), type: type});
}
}, {
types: {
object: function(value) {
return v.isObject(value) && !v.isArray(value);
},
array: v.isArray,
integer: v.isInteger,
number: v.isNumber,
string: v.isString,
date: v.isDate,
boolean: v.isBoolean
},
messages: {}
})
};

validate.formatters = {
Expand Down

0 comments on commit c54cd8e

Please # to comment.