Validate: Rename "values"->"elems", repurpose "values" to validate hash values
I'm breaking stuff left and right while I still can.
Idea: "key_names" validation?
Idea: "tuple" validation that works like "keys" but for arrays.
(i.e. { tuple => { $index => $schema } }, could make "missing" and
"unknown" work for arrays, too)
This commit is contained in:
parent
fa24ca53e3
commit
3fad7feec3
2 changed files with 82 additions and 51 deletions
120
FU/Validate.pm
120
FU/Validate.pm
|
|
@ -13,15 +13,18 @@ my %builtin = map +($_,1), qw/
|
|||
default
|
||||
onerror
|
||||
trim
|
||||
values scalar sort unique
|
||||
keys unknown missing
|
||||
elems scalar sort unique
|
||||
keys values unknown missing
|
||||
func
|
||||
/;
|
||||
|
||||
my %type_vals = map +($_,1), qw/scalar hash array any/;
|
||||
my %unknown_vals = map +($_,1), qw/remove reject pass/;
|
||||
my %missing_vals = map +($_,1), qw/create reject ignore/;
|
||||
my %implied_type = qw/keys hash unknown hash values array sort array unique array/;
|
||||
my %implied_type = qw/
|
||||
keys hash values hash unknown hash
|
||||
elems array sort array unique array scalar array
|
||||
/;
|
||||
my %sort_vals = (
|
||||
str => sub($x,$y) { $x cmp $y },
|
||||
num => sub($x,$y) { $x <=> $y },
|
||||
|
|
@ -119,9 +122,9 @@ sub _compile($schema, $custom, $rec, $top, $validations=$top->{validations}) {
|
|||
$top->{type} = $type;
|
||||
}
|
||||
|
||||
if ($name eq 'values') {
|
||||
$top->{values} ||= _new;
|
||||
_compile($val, $custom, $rec-1, $top->{values});
|
||||
if ($name eq 'elems' || $name eq 'values') {
|
||||
$top->{$name} ||= _new;
|
||||
_compile($val, $custom, $rec-1, $top->{$name});
|
||||
next;
|
||||
}
|
||||
|
||||
|
|
@ -173,35 +176,51 @@ sub compile($pkg, $schema, $custom={}) {
|
|||
}
|
||||
|
||||
|
||||
sub _validate_keys {
|
||||
my @err;
|
||||
for my ($k, $s) ($_[0]{keys}->%*) {
|
||||
if (!exists $_[1]{$k}) {
|
||||
next if $s->{missing} && $s->{missing} eq 'ignore';
|
||||
return { validation => 'missing', key => $k } if $s->{missing} && $s->{missing} eq 'reject';
|
||||
$_[1]{$k} = ref $s->{default} eq 'CODE' ? $s->{default}->() : $s->{default} // undef;
|
||||
next if exists $s->{default};
|
||||
}
|
||||
sub _validate_hash {
|
||||
my $c = $_[0];
|
||||
|
||||
my $r = _validate($s, $_[1]{$k});
|
||||
if ($r) {
|
||||
$r->{key} = $k;
|
||||
push @err, $r;
|
||||
if ($c->{keys}) {
|
||||
my @err;
|
||||
for my ($k, $s) ($c->{keys}->%*) {
|
||||
if (!exists $_[1]{$k}) {
|
||||
next if $s->{missing} && $s->{missing} eq 'ignore';
|
||||
return { validation => 'missing', key => $k } if $s->{missing} && $s->{missing} eq 'reject';
|
||||
$_[1]{$k} = ref $s->{default} eq 'CODE' ? $s->{default}->() : $s->{default} // undef;
|
||||
next if exists $s->{default};
|
||||
}
|
||||
|
||||
my $r = _validate($s, $_[1]{$k});
|
||||
if ($r) {
|
||||
$r->{key} = $k;
|
||||
push @err, $r;
|
||||
}
|
||||
}
|
||||
return { validation => 'keys', errors => [ sort { $a->{key} cmp $b->{key} } @err ] } if @err;
|
||||
}
|
||||
|
||||
if ($c->{values}) {
|
||||
my @err;
|
||||
for my ($k, $v) ($_[1]->%*) {
|
||||
my $r = _validate($c->{values}, $v);
|
||||
if ($r) {
|
||||
$r->{key} = $k;
|
||||
push @err, $r;
|
||||
}
|
||||
}
|
||||
return { validation => 'values', errors => [ sort { $a->{key} cmp $b->{key} } @err ] } if @err;
|
||||
}
|
||||
return { validation => 'keys', errors => [ sort { $a->{key} cmp $b->{key} } @err ] } if @err;
|
||||
}
|
||||
|
||||
sub _validate_values {
|
||||
sub _validate_elems {
|
||||
my @err;
|
||||
for my $i (0..$#{$_[1]}) {
|
||||
my $r = _validate($_[0]{values}, $_[1][$i]);
|
||||
my $r = _validate($_[0]{elems}, $_[1][$i]);
|
||||
if ($r) {
|
||||
$r->{index} = $i;
|
||||
push @err, $r;
|
||||
}
|
||||
}
|
||||
return { validation => 'values', errors => \@err } if @err;
|
||||
return { validation => 'elems', errors => \@err } if @err;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -275,14 +294,14 @@ sub _validate_input {
|
|||
# Each branch below makes a shallow copy of the hash, so that further
|
||||
# validations can perform in-place modifications without affecting the
|
||||
# input.
|
||||
if (!$c->{unknown} || $c->{unknown} eq 'remove') {
|
||||
if (!$c->{keys} || ($c->{unknown} && $c->{unknown} eq 'pass')) {
|
||||
$_[1] = { $_[1]->%* };
|
||||
} elsif (!$c->{unknown} || $c->{unknown} eq 'remove') {
|
||||
$_[1] = { map +($_, $_[1]{$_}), grep $c->{keys}{$_}, keys $_[1]->%* };
|
||||
} elsif ($c->{unknown} && $c->{unknown} eq 'reject') {
|
||||
} else {
|
||||
my @err = grep !$c->{keys}{$_}, keys $_[1]->%*;
|
||||
return { validation => 'unknown', keys => \@err, expected => [ sort keys $c->{keys}->%* ] } if @err;
|
||||
$_[1] = { $_[1]->%* };
|
||||
} else {
|
||||
$_[1] = { $_[1]->%* };
|
||||
}
|
||||
|
||||
} elsif ($type eq 'array') {
|
||||
|
|
@ -294,8 +313,8 @@ sub _validate_input {
|
|||
# No need to do anything here.
|
||||
}
|
||||
|
||||
($c->{keys} && &_validate_keys) ||
|
||||
($c->{values} && &_validate_values) ||
|
||||
($type eq 'hash' && &_validate_hash) ||
|
||||
($c->{elems} && &_validate_elems) ||
|
||||
&_validate_rec ||
|
||||
($type eq 'array' && &_validate_array)
|
||||
}
|
||||
|
|
@ -340,8 +359,9 @@ sub errors($e, $prefix='') {
|
|||
my $val = $e->{validation};
|
||||
my $p = $prefix ? "$prefix: " : '';
|
||||
$val eq 'keys' ? map errors($_, $prefix.'.'._fmtkey($_->{key})), $e->{errors}->@* :
|
||||
$val eq 'values' ? map errors($_, $prefix.'.'._fmtkey($_->{key})), $e->{errors}->@* :
|
||||
$val eq 'missing' ? $prefix.'.'._fmtkey($e->{key}).': required key missing' :
|
||||
$val eq 'values' ? map errors($_, $prefix."[$_->{index}]"), $e->{errors}->@* :
|
||||
$val eq 'elems' ? map errors($_, $prefix."[$_->{index}]"), $e->{errors}->@* :
|
||||
$val eq 'unique' ? $prefix."[$e->{index_b}] value '"._fmtval($e->{value_a})."' duplicated" :
|
||||
$val eq 'required' ? "${p}required value missing" :
|
||||
$val eq 'type' ? "${p}invalid type, expected '$e->{expected}' but got '$e->{got}'" :
|
||||
|
|
@ -374,10 +394,10 @@ 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 I<password> field must be equivalent to that
|
||||
of a I<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.
|
||||
filled out. Recursive data structures are not supported. There is also no good
|
||||
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
|
||||
|
|
@ -521,14 +541,22 @@ like:
|
|||
]
|
||||
}
|
||||
|
||||
=item values => $schema
|
||||
|
||||
Implies C<< type => 'hash' >>, set a schema that is used to validate every hash
|
||||
value. Can be used together with I<keys>, in which case values must validate
|
||||
both this C<$schema> and the schema corresponding to the key.
|
||||
|
||||
=item unknown => $option
|
||||
|
||||
Implies C<< type => 'hash' >>, this option specifies what to do with keys in
|
||||
the input data that have not been defined in the I<keys> option. Possible
|
||||
values are I<remove> to remove unknown keys from the output data (this is the
|
||||
default), I<reject> to return an error if there are unknown keys in the input,
|
||||
or I<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!
|
||||
or I<pass> to pass through any unknown keys to the output data. Values for
|
||||
passed-through keys are only validated when the I<values> option is set,
|
||||
otherwise they are passed through as-is. This option has no effect when the
|
||||
I<keys> option is never set, in that case all values are always passed through.
|
||||
|
||||
In the case of I<reject>, the error object will look like:
|
||||
|
||||
|
|
@ -549,7 +577,8 @@ undef), I<reject> to return an error if the option is missing or I<ignore> to
|
|||
leave the key out of the returned data.
|
||||
|
||||
The default is I<create>, but if no I<default> option is set for this key then
|
||||
that is effectively the same as I<reject>.
|
||||
that is effectively the same as I<reject>. Values created through I<create> are
|
||||
still validated through I<values> if that has been set.
|
||||
|
||||
In the case of I<reject>, the error object will look like:
|
||||
|
||||
|
|
@ -557,15 +586,15 @@ In the case of I<reject>, the error object will look like:
|
|||
key => 'field'
|
||||
}
|
||||
|
||||
=item values => $schema
|
||||
=item elems => $schema
|
||||
|
||||
Implies C<< 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
|
||||
every element in the array. The schema definition may be a bare hashref or a
|
||||
validator returned by C<compile()>.
|
||||
|
||||
Failure is reported in a similar fashion to I<keys>:
|
||||
|
||||
{ validation => 'values',
|
||||
{ validation => 'elems',
|
||||
errors => [
|
||||
{ index => 1, validation => 'required' }
|
||||
]
|
||||
|
|
@ -626,8 +655,7 @@ 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',
|
||||
{ elems => {
|
||||
keys => {
|
||||
id => { uint => 1 },
|
||||
name => {}
|
||||
|
|
@ -641,7 +669,7 @@ examples:
|
|||
|
||||
# Contrived example: An array of strings, and we want
|
||||
# each string to start with a different character.
|
||||
{ values => { minlength => 1 },
|
||||
{ elems => { minlength => 1 },
|
||||
unique => sub { substr $_[0], 0, 1 }
|
||||
}
|
||||
|
||||
|
|
@ -845,7 +873,7 @@ used in that schema may get input with whitespace around it.
|
|||
|
||||
All validations used in a schema need to agree upon a single I<type> option.
|
||||
If a custom validation does not specify a I<type> option (and no type is
|
||||
implied by another validation such as I<keys> or I<values>), then the
|
||||
implied by another validation such as I<keys> or I<elems>), 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:
|
||||
|
|
@ -859,8 +887,8 @@ error:
|
|||
|
||||
The I<func> option is validated separately for each custom validation.
|
||||
|
||||
Multiple I<keys> and I<values> validations are merged into a single validation.
|
||||
So if you have multiple custom validations that set the I<values> option, a
|
||||
Multiple I<keys> and I<elems> validations are merged into a single validation.
|
||||
So if you have multiple custom validations that set the I<elems> option, a
|
||||
single combined schema is created that validates all array elements. The same
|
||||
applies to I<keys>: if the same key is listed in multiple custom validations,
|
||||
then the key must conform to all schemas. With respect to the I<unknown>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue