NAME
Data::Tubes::Plugin::Validator
DESCRIPTION
This module contains factory functions to generate tubes that ease validation of records.
The factory functions below have two names, one starting with validate_
and the other without this prefix. They are perfectly equivalent to each other, whereas the short version can be handier e.g. when using tube
or pipeline
from Data::Tubes.
FUNCTIONS
admit
$tube = admit(@validators); # OR
$tube = admit(@validators, \%args);
simple validator mainly aimed at providing regular expressions to be tested against text input. For this reason, the default input field from the record is raw
and not structured
like in "thoroughly", although you can override it.
The goal of this validator is to get rid of uninteresting parts quickly. For this reason, there is no on-the-fly collection of validation outcomes; see "thoroughly" if you need them.
The input record MUST be a hash reference except when input
is set to undef
. In this case it can be almost anything, although it SHOULD be a string if you plan using regular expressions (anything that stringifies will do, anyway).
@validators
can be either regular expressions or sub references. All of them MUST pass to get the input record out, otherwise you will get nothing (which means that the particular section of the pipeline will stop here for the specific record). You can revert this behaviour setting option refuse
to a true value.
Accepts the following options:
input
-
the name of the input field in the input record. Defaults to
raw
, which means that this validator is mainly aimed at filtering input records before they are parsed. You can set it toundef
and the input record itself will be validated, not a subfield; name
-
name of the tube, useful for debugging;
refuse
-
boolean flag to indicate that the test should be reversed, i.e. that all provided
@validators
MUST fail for getting the record on. If this is the case, you might be interested in using "validate_refuse", because it advertises your intentions a bit more clearly.
refuse
$tube = refuse(@validators); # OR
$tube = refuse(@validators, \%args);
This is the same as "validate_admit", except that the parameter refuse
is inconditionally set to a true value. No, you cannot revert this setting refuse
to a false value explicitly, because it would not be sane.
refuse_comment
$tube = refuse_comment(%args); # OR
$tube = refuse_comment(\%args);
Thin wrapper around "validate_refuse" to eliminate comment lines, defined as any line that starts with optional spaces and whose first non-space character is the hash #
.
refuse_comment_or_empty
$tube = refuse_comment(%args); # OR
$tube = refuse_comment(\%args);
Thin wrapper around "validate_refuse" to eliminate comment or empty lines, defined as any line that starts with optional spaces and whose first non-space character, if present, is the hash #
.
refuse_empty
$tube = refuse_comment(%args); # OR
$tube = refuse_comment(\%args);
Thin wrapper around "validate_refuse" to eliminate empty lines, defined as any line that only contains optional whitespaces.
thoroughly
$tube = thoroughly(@validators); # OR
$tube = thoroughly(@validators, \%args);
validate record according to provided @validators
.
Differently from other validators in this plugin:
the input record MUST be a hash reference;
the input to be validated is set via argument
input
, that defaults tostructured
(instead ofraw
). You might want to change this if you intend to use regular expression validators;one field in the hash (according to factory argument
output
, set tovalidation
by default) is set to the output of the validation operation.
Items in @validators
can be sub references, regex references or array references, as explained below. An optional hash reference at the end can carry options, see below for their explanation.
A validator basically boils down to a sub reference that is called to perform the validation, or a regular expression. It can be either provided directly as an item in @validators
, or embedded in an array reference, prefixed with a name and with optional additional parameters. Example:
$tube = thoroughly(
sub { $_[0]{foo} =~ /bar|baz/ }, # straight sub ref
[
'Number should be even',
sub { $_[0]{number} % 2 == 0 },
],
[
'Name of something else',
sub { ... },
@parameters
],
);
The validator function will be called in list context, like this:
my @outcome = $validator->(
$target, # what pointed by "input", or the whole record
$record, # the whole record, if necessary
\%args, # args passed to the factory
@parameters, # anything sub in the array ref version
);
The validator can:
return the empty list, in which case the validation is considered failed;
return a single value, that represents the outcome of the validation. Anything considered false by Perl means that the validation failed, otherwise the validation is considered a success;
return more values, the first representing the outcome of the validation as in the previous bullet, the following ones things that you want to track as the outcome of the validation (e.g. some explanation of what went wrong with the validation).
If one of the validators throws an exception, this will not be trapped unless wrapper
is set properly. See below if you want to catch exceptions and transform them into failed validation steps.
All validations are performed in the order provided in @validators
, independently of whether they succeed or fail. This is by design, so that you can provide a thorough feedback about what you think is wrong with the input data.
Validation outcomes are collected into an array of arrays that is eventually referenced by the record provided as output (which is the same as the input, only augmented). By default this array of arrays is referenced by key validation
, but you can control the key via option output
.
Normally, only failed validations are collected in the array, so that you can easily check if validation was successful at a later stage. You can decide to collect all outcomes via option keep_positives
.
By default, if the validation collection procedure does not collect anything (i.e. all validations were successful and keep_positives
is false), the output key is set to undef
, so that you can check for validation errors very quickly instead of checking the number of items in the array. If you prefer to receive an empty array instead, you can set option keep_empty
.
You can wrap the call to all your validators via an optional wrapper
sub reference. This means that the following call will be used instead:
my @outcome = $wrapper->(
$validator, # the validation function
$target, # what pointed by "input", or the whole record
$record, # the whole record, if necessary
\%args, # args passed to the factory
@parameters, # anything sub in the array ref version
);
In this case, your wrapper
function will be responsible for calling $validator
in the right way. You can use this e.g. to perform some adaptation of interface for either the input or the output of the validation sub. As a matter of fact, in this case $validator
is not even required to be a sub reference.
In addition to setting wrapper
to a sub reference, you can also set it to the string try
. This will wrap the call to the validator in a try
/catch
using Try::Catch, which you are supposed to have installed independently.
Allowed arguments are:
input
-
the name of the input field in the record. Defaults to
structured
, in the assumption that you will want to perform validation after parsing, but you can of course set it to whatever you want. If you set it toundef
, the whole input record will be considered the$target
for the validation. Keep in mind that each validator will always receive also a reference to the$record
as the second argument anyway; keep_empty
-
if all validators succeed and
keep_positives
below is false, the overall outcome of the validation process will be an empty array. This option allows you to control whether you want an empty array asoutput
in this case, or you prefer to receive a false value for quicker identification of no validation errors condition. Defaults to0
, i.e. a false value, meaning that you will receive an undefined value inoutput
in case all validations were successful; keep_positives
-
validations that are successful are normally discarded, as you are assumed to be interested into failures most. If you want an account of all the validation steps, instead, you can set this flag to a true value. Defaults to
0
, a false value, meaning that positive validations are discarded; name
-
the name of the tube, useful when debugging. Defaults to
validate with subs
; output
-
the name of the output field in the output record. Defaults to
validation
. wrapper
-
a subroutine to wrap each call to a validator. In this case,
thoroughly
will call the wrapper instead, passing as the first parameter the validator, then the list of parameter it would have passed to the validator itself.You can also pass the special value
try
, that allows you to set the following wrapper subroutine equivalent:use Try::Catch; sub { my ($validator, @parameters) = @_; return try { $validator->(@parameters); } catch { (0, $_); }; }
except that Try::Catch is loaded dynamically at runtime and no function is imported. This allows you to turn exceptions into failed validations (note that the first item in the expression inside the
catch
part is0
, i.e. a failed validation) where the exception iteself is passed as additional "reason" that is eventually collected in the outcome.
A few examples should be of help now.
First, an example with validators that all return a true or false value, hence there is nothing to trap:
my $v = thoroughly(
sub { $_[0]{foo} =~ /bar|baz/ },
['is-even' => sub { $_[0]{number} % 2 == 0 }],
['in-bounds' =>
sub { $_[0]{number} >= 10 && $_[0]{number} <= 21}]
);
my $o1 = $v->({structured => {foo => 'bar', number => 12}});
my $o2 = $v->({structured => {foo => 'bar', number => 13}});
my $o3 = $v->({structured => {foo => 'hey', number => 3}});
In all cases the output record contains a new validation
key, pointing to:
$o1
anundef
value$o2
an array reference like this:[ ['is-even', ''] ]
because the test
is-even
fails returning an empty string$o3
an array reference like this:[ ['validator-0', 0], # empty list transformed into "0" ['is-even', ''], # empty string from validator ['in-bound', ''] # empty string from validator ]
As you can see, in the case of the first test a name is automatically generated based on the index of the test in the list of validators.
Here's an example for trapping exceptions:
my $v= thoroughly(
sub { $_[0]{foo} =~ /bar|baz/ },
['is-even' =>
sub { ($_[0]{number} % 2 == 0) or die "odd\n" }],
['in-bounds' =>
sub { $_[0]{number} >= 10 or die "too low\n" }],
{wrapper => 'try'},
);
my $o4 = $v->({structured => {foo => 'bar', number => 13}});
my $o5 = $v->({structured => {foo => 'hey', number => 3}});
Again, you will get a validation
key in each output record, like this:
$o4
-
only the first test fails in this case, so this is what you get:
[ ['is-even', 0, "odd\n"] ]
$o5
-
all three tests fail, two with exception, leading to this:
[ ['validator-0', 0], # as before ['is-even', 0, "odd\n"], # exception to failure ['in-bound', 0, "too low"] # exception to failure ]
You hopefully get the idea at this point.
It's important to always remember the difference between the following validators:
sub { ($_[0]{number} % 2 == 0) or die "odd\n" };
sub { die "odd\n" if $_[0]{number} % 2 };
The second validator always fails: it either throws an exception, or returns a false value. This is not the case with the first one. Always remember to return a true value from your validators, like this:
sub { die "odd\n" if $_[0]{number} % 2; 1 }
(Yes, this actually happened while writing the tests...)
validate_admit
Alias for "admit".
validate_refuse
Alias for "refuse".
validate_refuse_comment
Alias for "refuse_comment".
validate_refuse_comment_or_empty
Alias for "refuse_comment_or_empty".
validate_refuse_empty
Alias for "refuse_empty".
validate_thoroughly
Alias for "thoroughly".
validate_with_subs
Alias for "with_subs".
with_subs
$tube = with_subs(@validators); # OR
$tube = with_subs(@validators, \%args);
This function is DEPRECATED and currently aliased to "thoroughly". It used to do all that "thoroughly" does, except handling regular expression validators; now ith supports them too, which is why a name change was necessary.
BUGS AND LIMITATIONS
Report bugs either through RT or GitHub (patches welcome).
AUTHOR
Flavio Poletti <polettix@cpan.org>
COPYRIGHT AND LICENSE
Copyright (C) 2016 by Flavio Poletti <polettix@cpan.org>
This module is free software. You can redistribute it and/or modify it under the terms of the Artistic License 2.0.
This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.