FormValidator::Tiny - A tiny form validator
version 0.004
use FormValidator::Tiny qw( :validation :predicates :filtesr );
use Email::Valid; # <-- for demonstration, not required
use Email::Address; # <-- for demonstration, not required
use Types::Standard qw( Int ); # <-- for demonstration, not required
validation_spec edit_user => [
login_name => [
required => 1,
must => limit_character_set('_', 'a-z', 'A-Z', '0-9'),
must => length_in_range(5, 16),
],
name => [
required => 1,
must => length_in_range(1, 100),
],
age => [
optional => 1,
into => '+',
must => Int,
must => number_in_range(13, '*'),
],
password => [
required => 1,
must => length_in_range(8, 72),
],
confirm_password => [
required => 1,
must => equal_to('password'),
],
email => [
required => 1,
must => length_in_range(5, 250),
must => sub { (
!!Email::Valid->address($_),
"That is not a well-formed email address."
) },
into => 'Email::Address',
],
groups => [
optional => 1,
into => split_by(' '),
into => '[]',
each_must => length_in_range(3, 20),
each_must => limit_character_set(
['_', 'a-z', 'A-Z'],
['_', '-', 'a-z', 'A-Z', '0-9'],
),
],
tags => [
optional => 1,
into => split_by(/\s*,\s*/),
each_into => split_by(/\s\*:\s*/, 2),
into => '{}',
key_must => length_in_range(3, 20),
key_must => qr/^(?:[A-Z][a-z0-9]*)(?:-[A-Z][a-z0-9]*)*)$/,
with_error => 'Tags keys must be of a form like "Favorite" or "Welcome-Message".',
value_must => length_in_range(1, 500),
value_must => limit_character_set('_', '-', 'a-z', 'A-Z', '0-9'),
],
];
# Somehow your web framework gets you a set of form parameters submitted by
# POST or whatever. GO!
my $params = web_framework_params_method();
my ($parsed_params, $errors) = validate_form edit_user => $params;
# You probably want better error handling
if ($errors) {
for my $field (keys %$errors) {
print "Error in $field: $_\n" for @{ $errors->{$field} };
}
}
# Form fields are valid, take action!
else {
do_the_thing(%$parased_params);
}
The API of this module is still under development and could change, but probably won't.
There are lots for form validators, but this one aims to be the one that just does one thing and does it well without involving anything else if it can. If you just need a small form validator without installing all of CPAN, this will do that. If you want to install all of CPAN and use a readable form validation spec syntax, I hope this will do that too.
This module requires Perl 5.18 or better as of this writing.
This module exports three sets of functions, each with their own export tag:
-
:validation
This is exported by default. It includes the two central functions provided by this interface,
validation_spec
andvalidate_form
. -
:predicates
This includes the built-in predicate helpers, used with
must
andmust
-like directives.- limit_character_set
- length_in_range
- equal_to
- number_in_range
- one_of
-
:filters
This includes the build-in filter helpers, used with
into
andinto
-like directives.- split_by
- trim
-
:all
All of the above.
validation_spec $spec_name => \@spec;
This defines a validation specification. It associates a specification named
$spec_name
with the current package. Any use of validate_form
within the
current package will use specifications named within the current package. The
following example would work fine as the "edit" spec defined in each controller
is in their respective package namespaces.
package MyApp::Controller::User;
validation_spec edit => [ ... ];
sub process_edits {
my ($self, $c) = @_;
my ($p, $e) = validate_form edit => $c->req->body_parameters;
...
}
package MyApp::Controller::Page;
validation_spec edit => [ ... ];
sub process_edits {
my ($self, $c) = @_;
my ($p, $e) = validate_form edit => $c->req->body_parameters;
...
}
If you want to define them into a different package, name the package as part of the spec. Similarly, you can validate_form using a spec defined in a different package by naming the package when calling "validate_form":
package MyApp::Forms;
validation_spec MyApp::Controller::User::edit => [ ... ];
package MyApp::Controller::User;
sub process_groups {
my ($self, $c) = @_;
my ($p, $e) = validate_form MyApp::Controller::UserGroup::edit => $c->req->body_parameters;
...
}
You can also define your validation specification as lexical variables instead:
my $spec = validation_spec [ ... ];
my ($p, $e) = validate_form $spec, $c->req->body_parameters;
For information about how to craft a spec, see the "VALIDATION SPECIFICATIONS" section.
my ($params, $errors) = validate_form $spec, $input_parameters;
Compares the given parameters against the named spec. The $input_parameters
may be provided as either a hash or an array of alternating key-value pairs. All
keys and values must be provided as strings.
The method returns two values. The first, $params
, is the parameters as far
as they have been validated so far. The second, $errors
is the errors that
have been detected.
The $params
will be provided as a hash. The keys of this hash will match the
keys given in the spec. Some keys may be missing if the provided
$input_parameters
did not contain values or those values are invalid.
If there are no errors, the $errors
value will be set to undef
. With
errors, this will be hash of arrays. The keys of the hash will also match the
keys in the spec. Only fields with a validation error will be set. Each value
is an array of strings, with each string being an error message describing a
validation failure.
must => limit_character_set(@sets)
must => limit_character_set(\@fc_sets, \@rc_sets);
This returns a subroutine that limits the allowed characters for an input. In the first form, the character set limits are applied to all characters in the value. In the second, the first array limits the characters permitted for the first character and the second limits the characters permitted for the rest.
Character sets may be provided as single letters (e.g., "_"), as named unicode character properties wrapped in square brackets (e.g., "[Uppercase_Letter]"), or as ranges connected by a hyphen (e.g., "a-z").
must => length_in_range('*', 10)
must => length_in_range(10, '*')
must => length_in_range(10, 100)
This returns a subroutine for use with must
declarations that asserts the
minimum and maximum string character length permitted for a value. Use an
asterisk to define no limit.
must => equal_to('field')
This returns a subroutine for use with must
declarations that asserts that
the value must be exactly equal to another field in the input.
must => number_in_range('*', 100)
must => number_in_range(100, '*')
must => number_in_range(100, 500)
must => number_in_range(exclusive => 100, exclusive => 500)
Returns a predicate for must that requires the integer to be within the given range. The endpoints are inclusive by default. You can add the word "exclusive" before a value to make the comparison exclusive instead. Using a '*' indicates no limit at that end of the range.
must => one_of(qw( a b c )),
Returns a predicate that requires the value to exactly match one of the enumerated values.
into => split_by(' ')
into => split_by(qr/,\s*/)
into => split_by(' ', 2)
into => split_by(qr/,\s*/, 10)
Returns an into filter that splits the string into an array. The arguments are
similar to those accepted by Perl's built-in split
.
into => trim
into => trim('left')
into => trim('right')
Returns an into filter that trims whitespace from the input value. You can provide an argument to trim only the left whitespace or the right whitespace.
The validation specification is an array reference. Each key names a field to validate. The value is an array of processing declarations. Each processing declaration is a key-value pair. The inputs will be processed in the order they appear in the spec. The key names the type of processing. The value describes arguments for the processing. The processing declarations will each be executed in the order they appear. The same processor may be applied multiple times.
Input declarations modify the initial value and must be given at the very top of the list of declarations for a field before all others.
from => 'input_parameter_name'
Without this declaration, the validator pulls input from the parameter with the same name as the key named in the validation spec. This input declaration changes the key used for input.
multiple => 1
The multiple input declaration tells the validator whether to interpret the input parameter as a multiple input or not. Without this declaration or with it set to 0, the validator will interpret multiple inputs as a single value, ignoring all but the last. With this declaration, it treat the input as multiple items, even if there are 0 or 1.
trim => 0
The default behavior of "validate_form" is to trim whitespace from the beginning
and end of a value before processing. You can use the trim
declaration to
disable that.
Filtering declarations inserted into the validation spec will replace the input value with the newly filtered value at the point at which the declaration is encountered.
into => '+'
into => '?'
into => '?+'
into => '?perl'
into => '?yes!no',
into => '[]'
into => '{}'
into => 'Package::Name'
into => sub { ... }
into => TypeObject
This is a filter declaration that transforms the input using the named coercion.
-
Numeric
Numeric coercion is performed using the '+' argument. This will convert the value using Perl's built-in string-to-number conversion.
-
Boolean
Boolean coercion is performed using the '?' argument. This will convert the value to boolean. It does not use Perl's normal mechanism, though. Instead, it converts the string to boolean based on string length alone. If the string is empty, it is false. If it is not empty it is true.
-
Boolean by Numeric
Boolean by Numeric coercion is performed using the '?+' argument. This will first convert the string input to a number and then the number will be collapsed to a boolean value such that 0 is false and any other value is true.
-
Boolean via Perl
Boolean via Perl coercion is performed using the '?perl' argument. This will convert to boolean using Perl's usual boolean logic.
-
Boolean via Enumeration
Boolean via Enumeration coercion is performed using an argument that starts with a question mark, '?', and contains an exclamation mark, '!'. The value between the question mark and exclamation mark is the value that must be provided for a true value. The value provided between the exclamation mark and the end of string is the false value. Anything else will be treated as invalid and cause a validation error.
-
Array
Using a value of '[]' will make sure the value is treated as an array. This is a noop if the "multiple" declaration is set or if a "filter" returns an array. If the value is still a single, though, this will make sure the input value is placed inside an array references. This will also turn a hash value into an array.
-
Hash
Setting the declaration to '{}" will coerce the value to a hash. The even indexed values in the array will become keys and the odd indexed values in the array will become their respective values. If the value is not an array, it will turn a single value into a key/value pair with the key and the pair both being equal to the original value.
-
Package
A package coercion happens when the string given is a package name. This assumes that passing the input value to the
new
constructor of the named package will do the right thing. If you need anything more complicated than that, you should use a subroutine coercion. -
Subroutine
A subroutine coercion converts the value using the given subroutine. The current input value is passed as the single argument to the coercion (and also set as the localized copy of
$_
). The return value of the subroutine becomes the new input value. -
Type::Tiny Coercion
If an object is passed that provides a
coerce
method. That method will be called on the current input value and the result will be used as the new input value.
each_into => '+'
each_into => '?'
each_into => '?+'
each_into => '?perl'
each_into => '?yes!no',
each_into => '[]'
each_into => '{}'
each_into => 'Package::Name'
each_into => sub { ... }
each_into => TypeObject
Performs the same coercion as "into", but also works with arrays and hashes. It will apply the filter to a single value or to all elements of an array or to all keys and values of a hash.
key_into => '+'
key_into => '?'
key_into => '?+'
key_into => '?perl'
key_into => '?yes!no',
key_into => '[]'
key_into => '{}'
key_into => 'Package::Name'
key_into => sub { ... }
key_into => TypeObject
Performs the same coercion as "into", but also works with arrays and hashes. It will apply the filter to a single value or to all even index elements of an array or to all keys of a hash.
value_into => '+'
value_into => '?'
value_into => '?+'
value_into => '?perl'
value_into => '?yes!no',
value_into => '[]'
value_into => '{}'
value_into => 'Package::Name'
value_into => sub { ... }
value_into => TypeObject
Performs the same coercion as "into", but also works with arrays and hashes. It will apply the filter to a single value or to all odd index elements of an array or to all values of a hash.
required => 1
required => 0
optional => 1
optional => 0
It is strongly recommended that all fields add this declaratoi immediately after the input declarations, if any.
When required is set (or optional is set to 0), an initial validation check is inserted that will fail if a value is not provided for this field. That value must contain at least one character (after trimming, if trimming is not disabled).
When optional is set (or required is set to 0), an initial validaiton check is inserted that will shortcircuit the rest of the validation if no value is provided.
must => qr/.../
must => sub { ... }
must => TypeObject
This declaration states that the input given must match the described predicate. The module supports three kinds of predicates:
-
Regular Expression
This will match the given regular expression against the input. It is recommended that the regular expression start with "^" or "\A" and end with "$" or "\z" to force a total string match.
The error message for these validates is not very good, so you probably want to combine use of this kind of predicate with a following "with_error" declaration.
-
Subroutine
($valid, $message) = $code->($value, \%fields);
The subroutine will be passed two values. The first is the input to test (which will also be set in the localalized copy of
$_
). This second value passed is rest of the input as processing currently stands.The return value must be a two elements list.
-
The first value returned is a boolean indicating whether the validation has passed. A true value (like 1) means validation passes and there's no error. A false value (like 0) means validation does not pass and an error has occured.
There is a third option, which is to return
undef
. This indicates that validaton should stop here. This is neither a success nor a failure. The value processed so far will be ignored, but no error message is returned either. Any further declarations for the field will be ignored as well.Returning
undef
allows custom code to shortcircuit validation in exactly the same was as settingoptional
. -
The second value is the error message to use. It is acceptable to return an error message even if the first value is a true or undefined value. In that case, the error message will be ignored.
-
-
Type::Tiny Object
The third option is to use a Type::Tiny-style type object. The "validate_form" routine merely checks to see if it is an object that provides a
check
method or avalidate_form
method. If it provides acheck
method, that method will be called and the boolean value returned will be treated as the success or failure to validate. In this case, the error message will be pulled from a call toget_message
, if such a method is provided. In thevalidate_form
case, it will be called and a true value will be treated as the error message and a false value as validation success.It is my experience that the error messages provided by Type::Tiny and similar type systems are not friendly for use with end-uers. As such, it is recommended that you provide a nicer error message with a following "with_error" declaration.
each_must => qr/.../
each_must => sub { ... }
each_must => TypeObject
This declaration establishes validation rules just like "must", but applies the test to every value. If the input is an array, that will apply to every value. If the input is a hash, it will apply to every key and every value of the hash. If it is a single scalar, it will apply to that single value.
key_must => qr/.../
key_must => sub { ... }
key_must => TypeObject
This is very similar to each_must
, but only applies to keys. It will apply to
a single value, or to the even index values of an array, or to the keys of a
hash.
value_must => qr/.../
value_must => sub { ... }
value_must => TypeObject
This is very similar to each_must
and complement of key_must
. It will
apply to a single value, or to the odd index values of an array, or to the
values of a hash.
with_error => 'Error message.'
with_error => sub { ... }
This defines the error message to associate with the previous must
,
each_must
, key_must
, value_must
, into
, required
, and optional
declaration. This will override any other associated message.
If you would like to provide a different message based on the input, you may provide a subroutine.
The validation specifications are defined in each packages where
"validation_spec" is called. This is done through a package variable named
%FORM_VALIDATOR_TINY_SPECIFICATION
. If you really need to use that variable
for something else or if defining global package variables offends you, you can
use the return value form of validation_spec
, which will avoid creating this
variable.
If you stick to the regular interface, however, this variable will be
established the first time validation_spec
is called. The spec names are the
keys and the values have no documented definition. If you want to see what they
are, you must the read the code, but there's no guarantee that the internal
representation of this variable will stay the same in future releases.
Validate::Tiny is very similar to this module in purpose and goals, but with a different API.
Andrew Sterling Hanenkamp hanenkamp@cpan.org
This software is copyright (c) 2017 by Qubling Software LLC.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.