How it works

You should define a structure:
FIELD_NAME: VALIDATION_RULE

FIELD_NAME is a name of field to validate

VALIDATION_RULE is a name of function to be called. It can be function that builds validator or maybe just a validation function itself. Some arguments cab be passed to the function - "{ VALIDATION_RULE: ARGUMENTS }". You may pass an array of validation rules. If you want to pass several arguments you should use array.

So, the idea is that there is a tiny core which can be easly extended with new rules.

Validation Rules

Be aware that all standard rules just skip checking empty values.
So, empty string will pass next validation - "first_name: { min_length: [10] }". We have special rules "required" and "not_empty" to check that value is present.
This allows us to use the same rules for not required fields.

first_name: { min_length: [10] } # name is optional. We will check length only if "first_name" was passed
first_name: [ 'required', { min_length: [10] } ] # check that the name is present and validate length

Types coercing

You should treat rules as expectations. Therefore, all implementation make types coercing in numeric rules. This is reasonable because if someone says "positive_integer" then he expects to receive positive integer after validation.

We follow the same logic for string rules. We compare values as string as rule are "string rules" but return what developer expects. For example, if we use "eq" rule with input:

What about the test suite? The test suite is stored in JSON format, therefore we can rely only on types that are supported by JSON. So, if we have input {field: "1"}, rules {field: 'positive_integer'} then the output will be { field: 1 }. As I see it should work. For example, JavaScript will coerce "1" to 1. And the test will pass. Perl will do nothing. And the test will pass. Please, let me know if there are any issues with this.

Standard rules that should be supported by every implementation:

Common Rules

required

not_empty

not_empty_list

any_object

String Rules

string

eq

one_of

max_length

min_length

length_between

length_equal

like

Numeric Rules

integer

positive_integer

decimal

positive_decimal

max_number

min_number

number_between

Special Rules

email

url

iso_date

equal_to_field

Metarules

nested_object

list_of

list_of_objects

list_of_different_objects

or

Modifiers (previously - "Filter rules")

trim

to_lc

to_uc

remove

leave_only

default

Common Rules

required

Error code: 'REQUIRED'

Example:

{first_name: 'required'}

not_empty

Error code: 'CANNOT_BE_EMPTY'

Example:

{first_name: 'not_empty'}

not_empty_list

Checks that list contains at least one element

Error code: 'CANNOT_BE_EMPTY' (If the value is not present or list is empty). If the value is present but it is not a list the error code will be 'FORMAT_ERROR'

Example:

{products_ids: 'not_empty_list'}

any_object

Checks that the value is an plain object

Error code: 'FORMAT_ERROR' if the value is not a plain object.

Example:

{address: 'any_object'}

String Rules

string

Checks that value is primitive type and coerces it to the string. Better use more strict rules.

In this example validator will look on "product_type" in each object and depending on it will use corresponding set of rules

or (experimental)

The rule takes sets of other rules and applies them one after another until successful validation.

This rule simplifies alias creation. See "Aliases" section. It is a good idea to define custom error for your alias.

Errors: As we pass several sets of rules it is unclear what should validator return in case of several errors. Currenly we return the last error (this bahavior can change in future, this rule is experimental).

You use 'address' in a lot of your objects (user address in user, office address in office object and others) and you want to reuse the same address rules in all places. You have two ways: write custom rule 'valid_address' or assign rules to a variable and just reuse the variable. The first way requires much time and coding. Moreover, you cannot save new rule implementation is serialzed JSON file. The second way is much easier and you can store rule implemetation in JSON file but you cannot store user validation rules. Moreover, the second way does not allow you to redefine error code for address.

From v0.4 you have the third way - rule aliasing.

You can register aliases for complex rules. The way how you register an alias depends on the implementation but the way how use and describe it is covered by the specification. This appoach allows you store aliases in serialized JSON files and then use them across different implementations.

Developers Guide

Your implementation should support all validation rules described in "Validation Rules"

Your implementation should support "Rules aliasing"

Your implementation should return error codes descibed in the specification

It should be easy to implement own rules

Please, use provided "test_suite" to ensure that your implementation works correctly

Changes

v0.2

Added not_empty_list rule with test cases

v0.3

Added filter rule "trim"

Added filter rule "to_lc"

Added filter rule "to_uc"

v0.4

Added special rules (url, iso_date)

Added filter rules (remove, leave_only)

Add flags 'i' flag support to the "like" rule

Introduces new syntax for "one_of" and "list_of" rules. (See "Syntax changes for 'one_of' and 'list_of' rules")

Rules aliasing (with custom errors)

v2.0

Switched to semver. New release version is 2.0

Unified approach to types handling

"Base rules" renamed to "Common rules".

"Filter rules" renamed to "Modifiers". They do not validate anything, just modify data.

"Helper rules" renamed to "Metarules" as this rules are for describing other rules

Added common rule "any_object"

Added string rule "string"

Added string rule "eq"

Added metarule "variable_object"

Added metarule "or"

Added modifier "default"

Add more edge cases to test suite

Add experimental status to the "like" rule

Syntax changes for 'one_of' and 'list_of' rules

Old syntax {one_of: [['val1', 'val2']]} was hard to remember for many people. The idea was that list of allowed values should be passed as array reference. So, {one_of: [['val1', 'val2']]} becomes one_of(['val1', 'val2']) but it is not always clear for users. Therefore, it was decided to introduce a new syntax. Now you can write {one_of: ['val1', 'val2']} that becomes one_of('val1', 'val2'). The main problem with it that you do not know how many arguments will be passed to 'one_of'. Moreover, you should support both syntaxes for backward compatibility (test suite contains tests for both cases). But it was decided that "one_of" and "list_of" rules can handle both syntaxes by themselves.

TODO

Add JSON Schema comparison

Describe internals with detailed step-by-step example

Write developers guide

LICENSE AND COPYRIGHT

Copyright 2012 Viktor Turskyi.

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.