Validate: Add empty() and coerce() methods
Implementing the undocumented coerce_for_json() method from TUWF and elm_empty() from VNDB.
This commit is contained in:
parent
efa63ca96a
commit
8b807e6dcf
2 changed files with 131 additions and 5 deletions
100
FU/Validate.pm
100
FU/Validate.pm
|
|
@ -61,6 +61,10 @@ our $re_weburl = qr/^https?:\/\/$re_domain(?::[1-9][0-9]{0,5})?(?:\/[^\s<>"]*
|
|||
our $re_date = qr/^(?:19[0-9][0-9]|20[0-9][0-9])-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])$/;
|
||||
|
||||
|
||||
# There's a special '_scalartype' option used for coerce() and empty(), with the following values:
|
||||
# 0/undef/missing: string, 1:num, 2:int, 3:bool
|
||||
# The highest number, i.e. most restrictive type, is chosen when multiple validations exist.
|
||||
|
||||
our %default_validations = (
|
||||
regex => sub($reg) {
|
||||
# Error objects should be plain data structures so that they can easily
|
||||
|
|
@ -78,12 +82,12 @@ our %default_validations = (
|
|||
maxlength => sub($v) { _length $v, undef, $v },
|
||||
length => sub($v) { _length $v, ref $v eq 'ARRAY' ? @$v : ($v, $v) },
|
||||
|
||||
bool => { type => 'any', func => sub { my $r = to_bool $_[0]; return {} if !defined $r; $_[0] = $r; 1 } },
|
||||
anybool => { type => 'any', default => false, func => sub { $_[0] = $_[0] ? true : false; 1 } },
|
||||
bool => { _scalartype => 3, type => 'any', func => sub { my $r = to_bool $_[0]; return {} if !defined $r; $_[0] = $r; 1 } },
|
||||
anybool => { _scalartype => 3, type => 'any', default => false, func => sub { $_[0] = $_[0] ? true : false; 1 } },
|
||||
|
||||
num => [ _reg($re_num), func => sub { $_[0] = $_[0]*1; 1 } ],
|
||||
int => [ _reg($re_int), func => sub { return { message => 'integer out of range' } if $_[0] < -9223372036854775808 || $_[0] > 9223372036854775807; $_[0] = int $_[0]; 1 } ],
|
||||
uint => [ _reg($re_uint), func => sub { return { message => 'integer out of range' } if $_[0] > 18446744073709551615; $_[0] = int $_[0]; 1 } ],
|
||||
num => [ _scalartype => 1, _reg($re_num), func => sub { $_[0] = $_[0]*1; 1 } ],
|
||||
int => [ _scalartype => 2, _reg($re_int), func => sub { return { message => 'integer out of range' } if $_[0] < -9223372036854775808 || $_[0] > 9223372036854775807; $_[0] = int $_[0]; 1 } ],
|
||||
uint => [ _scalartype => 2, _reg($re_uint), func => sub { return { message => 'integer out of range' } if $_[0] > 18446744073709551615; $_[0] = int $_[0]; 1 } ],
|
||||
min => sub($min) { +{ num => 1, func => sub { $_[0] >= $min ? 1 : { expected => $min, got => $_[0] } } } },
|
||||
max => sub($max) { +{ num => 1, func => sub { $_[0] <= $max ? 1 : { expected => $max, got => $_[0] } } } },
|
||||
range => sub { [ min => $_[0][0], max => $_[0][1] ] },
|
||||
|
|
@ -152,6 +156,11 @@ sub _compile($schema, $custom, $rec, $top, $validations=$top->{validations}) {
|
|||
next;
|
||||
}
|
||||
|
||||
if ($name eq '_scalartype') {
|
||||
$top->{_scalartype} = $val if ($top->{_scalartype}||0) < $val;
|
||||
next;
|
||||
}
|
||||
|
||||
if ($builtin{$name}) {
|
||||
confess "Invalid value for 'missing': $val" if $name eq 'missing' && !$missing_vals{$val};
|
||||
confess "Invalid value for 'unknown': $val" if $name eq 'unknown' && !$unknown_vals{$val};
|
||||
|
|
@ -353,6 +362,37 @@ sub validate($c, $input) {
|
|||
}
|
||||
|
||||
|
||||
sub coerce {
|
||||
my $c = $_[0];
|
||||
my %opt = @_[2..$#_];
|
||||
if (!defined $_[1]) {
|
||||
$_[1] = undef;
|
||||
} elsif ($c->{_scalartype}) {
|
||||
$_[1] = $c->{_scalartype} == 3 ? !!$_[1] : $c->{_scalartype} == 2 ? int $_[1] : $_[1]+0;
|
||||
} elsif (!$c->{type} || $c->{type} eq 'scalar') {
|
||||
$_[1] = "$_[1]";
|
||||
} elsif ($c->{type} eq 'array' && $c->{elems} && ref $_[1] eq 'ARRAY') {
|
||||
coerce($c->{elems}, $_, %opt) for $_[1]->@*;
|
||||
} elsif ($c->{type} eq 'hash' && $c->{keys} && ref $_[1] eq 'HASH') {
|
||||
$opt{unknown} ||= $c->{unknown};
|
||||
delete @{$_[1]}{ grep !$c->{keys}{$_}, keys $_[1]->%* }
|
||||
if $opt{unknown} && $opt{unknown} ne 'pass';
|
||||
$_[1]{$_} = exists $_[1]{$_} ? coerce($c->{keys}{$_}, $_[1]{$_}, %opt) : empty($c->{keys}{$_})
|
||||
for keys $c->{keys}->%*;
|
||||
}
|
||||
return $_[1];
|
||||
}
|
||||
|
||||
|
||||
sub empty($c) {
|
||||
return ref $c->{default} eq 'CODE' ? $c->{default}->(undef) : $c->{default} if exists $c->{default};
|
||||
return [] if $c->{type} && $c->{type} eq 'array';
|
||||
return $c->{keys} ? +{ map +($_, empty($c->{keys}{$_})), keys $c->{keys}->%* } : {} if $c->{type} && $c->{type} eq 'hash';
|
||||
return undef if $c->{type} && $c->{type} eq 'any';
|
||||
# Only scalar types remain
|
||||
return !$c->{_scalartype} ? '' : $c->{_scalartype} == 3 ? !1 : 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
package FU::Validate::err;
|
||||
|
|
@ -445,6 +485,56 @@ I<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 L</Schema Definition> below.
|
||||
|
||||
Additional utility methods:
|
||||
|
||||
=over
|
||||
|
||||
=item $validator->empty
|
||||
|
||||
Returns an "empty" value that roughly follows the data structure described by
|
||||
the schema. The returned value does not necessarily validate but can still be
|
||||
useful as a template. Works roughly as follows:
|
||||
|
||||
=over
|
||||
|
||||
=item * If the schema has a I<default>, then that is returned.
|
||||
|
||||
=item * If the schema describes a hash, then a hash is returned with each key
|
||||
in I<keys> initialized to an empty value.
|
||||
|
||||
=item * If the schema describes an array, an empty array is returned.
|
||||
|
||||
=item * If the schema describes a bool, return C<false>.
|
||||
|
||||
=item * If the schema describes a number, return C<0>.
|
||||
|
||||
=item * If the schema describes a string, return C<''>.
|
||||
|
||||
=item * Otherwise, return C<undef>.
|
||||
|
||||
=back
|
||||
|
||||
=item $validator->coerce($input, %opt)
|
||||
|
||||
Perform in-place coercion of C<$input> to the data types described by the
|
||||
schema. Also returns the modified C<$input> for convenience. This method assumes
|
||||
that C<$input> already has the general structure described by the schema and is
|
||||
mainly useful to ensure that encoding the value as JSON will end up with the
|
||||
correct data types. i.e. booleans are encoded as booleans, integers as integers
|
||||
(truncating if necessary), numbers as numbers, etc.
|
||||
|
||||
If an input hash is missing keys described in the schema, then those are
|
||||
created with C<< ->empty >>. If the schema has I<unknown> set to either
|
||||
I<reject> or I<remove>, unknown keys are removed. This behavior can be
|
||||
overriden by passing different I<unknown> value in C<%opt>.
|
||||
|
||||
This method does NOT perform any sort of validation and will happily pass
|
||||
through garbage if the given C<$input> does not follow the structure of the
|
||||
schema. It's basically a faster and lousier normalization-only alternative to
|
||||
C<< ->validate() >>.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head1 Schema Definition
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue