NAME
FU::Validate - Data and form validation and normalization
EXPERIMENTAL
This module is still in development and there will likely be a few breaking API changes, see the main FU module for details.
DESCRIPTION
This module provides an easy and simple interface for data validation. It can handle most types of data structures (scalars, hashes, arrays and nested data structures), and has some conveniences for validating form-like data.
That this module will not solve all your input validation problems. It can validate the format and the structure of the data, but it does not support validations that depend on other input values. For example, it is not possible to specify that the contents of a password field must be equivalent to that of a confirm_password field, but you can specify that both fields need to be filled out. Recursive data structures are not supported. There is also no built-in support for validating hashes with dynamic keys or arrays where not all elements conform to the same schema. These could technically still be validated with custom validations, but it won't be as convenient.
This module is designed to validate any kind of program input after it has been parsed into a Perl data structure. It should not be used to validate function parameters within Perl code. In fact, the correct answer to "how do I validate function parameters?" is "don't, document your assumptions instead".
Validation API
To validate some input, you first need a schema. A schema can be compiled as follows:
my $validator = FU::Validate->compile($schema, $validations);
$schema
is the schema that describes the data to be validated (see "SCHEMA DEFINITION" below) and $validations
is an optional hashref containing custom validations that $schema
can refer to.
To validate input, run:
my $result = $validator->validate($input);
$input
is the data to be validated, and the $result
object is described below.
Both compile()
and validate()
may throw an error if the $validations
or $schema
are invalid. Errors in the $input
should never cause an error to be thrown, since these are always reported in the $result
object.
This module takes great care that $input
is not being modified in place, even if data normalization is being performed. The normalized data can be read from the $result
object.
Result object
The $result
object returned by validate()
overloads boolean context, so you can check if the validation succeeded with a simple if statement:
my $result = $validator->validate(..);
if($result) {
# Success!
my $data = $result->data;
} else {
# Input failed to validate...
my $error = $result->err;
}
In addition, the result object implements the following methods:
- data()
-
Returns the validated and normalized data. This method throws an error if validation failed, so if you're lazy and don't want to bother too much with proper error reporting, you can safely validate-and-die in a single step:
my $validated_data = $v->validate(..)->data;
(Note regarding reference semantics: The returned data will usually be a (possibly modified) copy of
$input
, but may in some cases still have nested references to data in$input
- so if you are working with nested hashrefs, arrayrefs or other objects and are going to make modifications to the values embedded within them, these changes may or may not also affect the values in the original$input
. Make a deep copy of the data if you're concerned about this). - err()
-
Returns undef if validation succeeded, an error object otherwise.
An error object is a hashref containing at least one key: validation, which indicates the name of the validation that failed. Additional keys with more detailed information may be present, depending on the validation. These are documented in "SCHEMA DEFINITION" below.
SCHEMA DEFINITION
A schema is a hashref, each key is the name of a built-in option or of a validation to be performed. None of the options or validations are required, but some built-ins have default values. This means that the empty schema {}
is actually equivalent to:
{ type => 'scalar',
rmwhitespace => 1,
default => \'required',
missing => 'create',
}
Built-in options
- type => $type
-
Specify the type of the input, this can be scalar, array, hash or any. If no type is specified or implied by other validations, the default type is scalar.
Upon failure, the error object will look something like:
{ validation => 'type', expected => 'hash', got => 'scalar' }
- default => $val
-
If not set, or set to
\'required'
(note: scalarref), then a value is required for this field. Specifically, this means that a value must exist and must not beundef
or an empty string, i.e.exists($x) && defined($x) && $x ne ''
.If set to any other value, then the input is considered optional and the given
$val
is returned instead. If$val
is a CODE reference, the subroutine is called with the original value (which is either no argument, undef or an empty string) and the return value of the subroutine is used as value instead.The empty check is performed after rmwhitespace and before any other validations. So a string containing only whitespace is considered an empty string and will be treated according to this default option. As an additional side effect, other validations will never get to validate undef or an empty string, as these values are either rejected or substituted with a default.
- onerror => $val
-
Instead of reporting an error, return
$val
if this input fails validation for whatever reason. Setting this option in the top-level schema ensures that the validation will always succeed regardless of the input.If
$val
is a CODE reference, the subroutine is called with the result object for this validation as its first argument. The return value of the subroutine is then returned for this validation. - rmwhitespace => 0/1
-
By default, any whitespace around scalar-type input is removed before testing any other validations. Setting rmwhitespace to a false value will disable this behavior.
- keys => $hashref
-
Implies
type => 'hash'
, this option specifies which keys are permitted, and how to validate the values. Each key in$hashref
corresponds to a key with the same name in the input. Each value is a schema definition by which the value in the input will be validated. The schema definition may be a bare hashref or a validator returned bycompile()
. If a key is not present in the input hash, it will be created in the output with the default value (or undef), but see the missing option for how to change that behavior.For example, the following schema specifies that the input must be a hash with three keys:
{ type => 'hash', keys => { username => { maxlength => 16 }, password => { minlength => 8 }, email => { default => '', email => 1 } } }
If validation on one or more keys fail, the error object that is returned looks like:
{ validation => 'keys', errors => [ # List of error objects, each with an additional 'key' field. { key => 'username', validation => 'required' } # In this case, the username was required but either absent or empty. ] }
- unknown => $option
-
Implies
type => 'hash'
, this option specifies what to do with keys in the input data that have not been defined in the keys option. Possible values are remove to remove unknown keys from the output data (this is the default), reject to return an error if there are unknown keys in the input, or pass to pass through any unknown keys to the output data. Note that the values for passed-through keys are not validated against any schema!In the case of reject, the error object will look like:
{ validation => 'unknown', # List of unknown keys present in the input keys => ['unknown1', .. ], # List of known keys (which may or may not be present # in the input - that is checked at a later stage) expected => ['known1', .. ] }
- missing => $option
-
For values inside a hash keys schema, this option specifies what to do when the key is not present in the input data. Possible values are create to insert the key with a default value (if the default option is set, otherwise undef), reject to return an error if the option is missing or ignore to leave the key out of the returned data.
The default is create, but if no default option is set for this key then that is effectively the same as reject.
In the case of reject, the error object will look like:
{ validation => 'missing', key => 'field' }
- values => $schema
-
Implies
type => 'array'
, this defines the schema that is applied to every item in the array. The schema definition may be a bare hashref or a validator returned bycompile()
.Failure is reported in a similar fashion to keys:
{ validation => 'values', errors => [ { index => 1, validation => 'required' } ] }
- scalar => 0/1
-
Implies
type => 'array'
, this option will also permit the input to be a scalar. In this case, the input is interpreted and returned as an array with only one element. This option exists to make it easy to validate multi-value form inputs. For example, considerquery_decode()
in FU::Util: a parameter in a query string is decoded into an array if it is listed multiple times, a scalar if it only occcurs once. So we could either end up with:{ a => 1, b => 1 } # OR: { a => [1, 3], b => 1 }
With the scalar option, we can accept both forms for
a
and normalize into an array. The following schema definition can validate the above examples:{ type => 'hash', keys => { a => { type => 'array', scalar => 1 }, b => { } } }
- sort => $option
-
Implies
type => 'array'
, sort the array after validating its elements.$option
determines how the array is sorted, possible values are str for string comparison, num for numeric comparison, or a subroutine reference for custom comparison function. The subroutine must be similar to the one given to Perl'ssort()
function, except it should compare$_[0]
and$_[1]
instead of$a
and$b
. - unique => $option
-
Implies
type => 'array'
, require elements to be unique. That is, don't allow duplicate elements. There are several ways to specify what uniqueness means in this context:If
$option
is a subroutine reference, then the subroutine is given an element as first argument, and it should return a string that is used to check for uniqueness. For example, if array elements are hashes, and you want to check for uniqueness of a hash key named id, you can specify this asunique => sub { $_[0]{id} }
.Otherwise, if
$option
is true and the sort option is set, then the comparison function used for sorting is also used as uniqueness check. Two elements are the same if the comparison function returns0
.If
$option
is true and sort is not set, then the elements will be interpreted as strings, similar to settingunique => sub { $_[0] }
.All of that may sound complicated, but it's quite easy to use. Here's a few examples:
# This describes an array of hashes with keys 'id' and 'name'. { values => { type => 'hash', keys => { id => { uint => 1 }, name => {} } }, # Sort the array on 'id' sort => sub { $_[0]{id} <=> $_[1]{id} }, # And require that 'id' fields are unique unique => 1 } # Contrived example: An array of strings, and we want # each string to start with a different character. { values => { minlength => 1 }, unique => sub { substr $_[0], 0, 1 } }
On failure, this validation returns the following error object. This output assumes the first schema from the previous example.
{ validation => 'unique', # Index and value of element a index_a => 1, value_a => { id => 3, name => 'whatever' } # Index and value of duplicate element b index_b => 4, value_b => { id => 3, name => 'something else' }, # If string-based uniqueness was used, this is included as well: # key => '..' }
- func => $sub
-
Run the input through a subroutine to perform additional validation or normalization. The subroutine is only called after all other validations have succeeded. The subroutine is called with the input as its only argument. Normalization of the input can be done by assigning to the first argument or modifying its value in-place.
On success, the subroutine should return a true value. On failure, it should return either a false value or a hashref. The hashref will have the validation key set to func, and this will be returned as error object.
When func is used inside a custom validation, the returned error object will have its validation field set to the name of the custom validation. This makes custom validations to behave as first-class validations in terms of error reporting.
Standard validations
Standard validations are provided by the module. It is possible to override, re-implement and supplement these with custom validations. Internally, these are, in fact, implemented as custom validations.
- regex => $re
-
Implies
type => 'scalar'
. Validate the input against a regular expression. - enum => $options
-
Implies
type => 'scalar'
. Validate the input against a list of known values.$options
can be either a scalar (in which case that is the only permitted input), an array (listing all possible inputs) or a hash (where the hash keys are considered to be the list of permitted inputs). - minlength => $num
-
Minimum length of the input. The length is the string
length()
if the input is a scalar, the number of elements if the input is an array, or the number of keys if the input is a hash. - maxlength => $num
-
Maximum length of the input.
- length => $option
-
If
$option
is a number, then this specifies the exact length of the input. If$option
is an array, then this is a shorthand for[$minlength,$maxlength]
. - anybool => 1
-
Accept any value of any type as input, and normalize it to either
true
orfalse
according to Perl's idea of truth. - bool => 1
-
Require the input to be a boolean type as per
to_bool()
in FU::Util. - num => 1
-
Implies
type => 'scalar'
. Require the input to be a number formatted using the format permitted by JSON. Note that this is slightly more restrictive from Perl's number formatting, in that 'NaN', 'Inf' and thousand separators are not permitted. - int => 1
-
Implies
type => 'scalar'
. Require the input to be an (arbitrarily large) integer. - uint => 1
-
Implies
type => 'scalar'
. Require the input to be an (arbitrarily large) positive integer. - min => $num
-
Implies
num => 1
. Require the input to be larger than or equal to$num
. - max => $num
-
Implies
num => 1
. Require the input to be smaller than or equal to$num
. - range => [$min,$max]
-
Equivalent to
min => $min, max => $max
. - ascii => 1
-
Implies
type => 'scalar'
. Require the input to wholly consist of printable ASCII characters. - sl => 1
-
Implies
type => 'scalar'
. Require the input to be a single line of text. Useful for validating<input type="text">
form elements, which really should not result in multi-line input. - ipv4 => 1
-
Implies
type => 'scalar'
. Require the input to be an IPv4 address. - ipv6 => 1
-
Implies
type => 'scalar'
. Require the input to be an IPv6 address. Note that the IP address is not normalized, and fancy features such as IPv4-manned-IPv6 addresses are not permitted. - ip => 1
-
Require either
ipv4 => 1
oripv6 => 1
. - email => 1
-
Implies
type => 'scalar'
. Validate the email address against a monstrosity of a regular expression. This email validation is designed to catch obviously invalid addresses and addresses that, while compliant with some RFCs, will not be accepted by most actual SMTP implementations.Email validation is quite a minefield, see Data::Validate::Email for an alternative solution.
- weburl => 1
-
Implies
type => 'scalar'
. Requires the input to be ahttp://
orhttps://
url. - date => 1
-
Implies
type => 'scalar'
. Requires the input to be a date string in the form ofYYYY-MM-DD
. Does not validate that the day number is valid for the given the year and month.
Custom validations
Custom validations can be passed to compile()
as the $validations
hashref argument. A custom validation is, in simple terms, either a schema or a subroutine that returns a schema. The custom validation can then be referenced from other schemas.
Here's a simple example that defines and uses a custom validation named stringbool, which accepts either the string true or false.
my $validations = {
stringbool => { enum => ['true', 'false'] }
};
my $schema = { stringbool => 1 };
my $result = FU::Validate->compile($schema, $validations)->validate('true');
# $result->data() eq 'true'
A custom validation can also be defined as a subroutine, in which case it can accept options. Here is an example of a prefix custom validation, which requires that the string starts with the given prefix. The subroutine returns a schema that contains the func built-in option to do the actual validation.
my $validations = {
prefix => sub($prefix) {
return { func => sub { $_[0] =~ /^\Q$prefix/ } }
}
};
my $schema = { prefix => 'Hello, ' };
my $result = FU::Validate->compile($schema, $validations)->validate('Hello, World!');
Custom validations and built-in options
Custom validations can also set built-in options, but the semantics differ a little depending on the option. First, be aware that many of the built-in options apply to the whole schema and not just to the custom validation. For example, if the top-level schema sets rmwhitespace => 0
, then all validations used in that schema may get input with whitespace around it.
All validations used in a schema need to agree upon a single type option. If a custom validation does not specify a type option (and no type is implied by another validation such as keys or values), then the validation should work with every type. It is an error to define a schema that mixes validations of different types. For example, the following throws an error:
FU::Validate->compile({
# top-level schema says we expect a hash
type => 'hash',
# but the 'int' validation implies that the type is a scalar
int => 1
});
The keys, values and func
built-in options are validated separately for each custom validation. So if you have multiple custom validations that set the values option, then the array elements must validate all the listed schemas. The same applies to keys: If the same key is listed in multiple custom validations, then the key must conform to all schemas. With respect to the unknown option, a key that is mentioned in any of the keys options is considered "known".
All other built-in options follow inheritance semantics: These options can be set in a custom validation, and they are inherited by the top-level schema. If the same option is set in multiple validations a random one will be inherited, so that's not a good idea. The top-level schema can always override options set by custom validations.
Global custom validations
Instead of passing a $validations
argument every time you call compile()
, you can also add custom validations to the global list of built-in validations:
$FU::Validate::default_validations{stringbool} = { enum => ['true', 'false'] };
SEE ALSO
FU.
This module is a fork of TUWF::Validate.
COPYRIGHT
MIT.
AUTHOR
Yorhel <projects@yorhel.nl>