Validate: Rework API, ->validate() now throws error instead of result object
This is a slight simplification and removes the need to pass around partially normalized data. I've never found a use for the unsafe_data() method.
This commit is contained in:
parent
7839e7df78
commit
cbebc3a21e
3 changed files with 295 additions and 347 deletions
5
FU.pm
5
FU.pm
|
|
@ -639,8 +639,8 @@ sub _getfield($data, @a) {
|
|||
return $data->{$a[0]} if @a == 1 && !ref $a[0];
|
||||
require FU::Validate;
|
||||
my $schema = FU::Validate->compile(@a > 1 ? { keys => {@a} } : $a[0]);
|
||||
my $res = $schema->validate($data);
|
||||
fu->error(400, "Input validation failed") if !$res; # TODO: More detailed error message
|
||||
my $res = eval { $schema->validate($data) };
|
||||
fu->error(400, "Input validation failed") if $@; # TODO: More detailed error message
|
||||
return @a == 2 ? $res->data->{$a[0]} : $res->data;
|
||||
}
|
||||
|
||||
|
|
@ -659,7 +659,6 @@ sub formdata {
|
|||
if (fu->header('content-type')||'') ne 'application/x-www-form-urlencoded';
|
||||
FU::Util::query_decode($FU::REQ->{data});
|
||||
} || fu->error(400, $@);
|
||||
# TODO: Accept schema validation thing.
|
||||
_getfield $FU::REQ->{formdata}, @_;
|
||||
}
|
||||
|
||||
|
|
|
|||
284
FU/Validate.pm
284
FU/Validate.pm
|
|
@ -102,7 +102,7 @@ sub _compile($schema, $validations, $rec) {
|
|||
my @keys = keys $schema->{keys}->%* if $schema->{keys};
|
||||
|
||||
for my($name, $val) (%$schema) {
|
||||
if($builtin{$name}) {
|
||||
if ($builtin{$name}) {
|
||||
$top{$name} = $schema->{$name};
|
||||
next;
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ sub _compile($schema, $validations, $rec) {
|
|||
|
||||
# Inherit some builtin options from validations
|
||||
for my $t (@val) {
|
||||
if($top{type} && $t->{schema}{type} && $top{type} ne $t->{schema}{type}) {
|
||||
if ($top{type} && $t->{schema}{type} && $top{type} ne $t->{schema}{type}) {
|
||||
confess "Incompatible types, the schema specifies '$top{type}' but validation '$t->{name}' requires '$t->{schema}{type}'" if $schema->{type};
|
||||
confess "Incompatible types, '$t->[0]' requires '$t->{schema}{type}', but another validation requires '$top{type}'";
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ sub compile($pkg, $schema, $validations={}) {
|
|||
|
||||
delete $c->{schema}{default} if ref $c->{schema}{default} eq 'SCALAR' && ${$c->{schema}{default}} eq 'required';
|
||||
|
||||
if(exists $c->{schema}{sort}) {
|
||||
if (exists $c->{schema}{sort}) {
|
||||
my $s = $c->{schema}{sort};
|
||||
$c->{schema}{sort} =
|
||||
ref $s eq 'CODE' ? $s
|
||||
|
|
@ -178,186 +178,169 @@ sub compile($pkg, $schema, $validations={}) {
|
|||
}
|
||||
|
||||
|
||||
sub _validate_rec($c, $input) {
|
||||
sub _validate_rec {
|
||||
my $c = $_[0];
|
||||
|
||||
# hash keys
|
||||
if($c->{schema}{keys}) {
|
||||
if ($c->{schema}{keys}) {
|
||||
my @err;
|
||||
for my ($k, $s) ($c->{schema}{keys}->%*) {
|
||||
if(!exists $input->{$k}) {
|
||||
if (!exists $_[1]{$k}) {
|
||||
next if $s->{schema}{missing} eq 'ignore';
|
||||
return [$input, { validation => 'missing', key => $k }] if $s->{schema}{missing} eq 'reject';
|
||||
$input->{$k} = ref $s->{schema}{default} eq 'CODE' ? $s->{schema}{default}->() : $s->{schema}{default} // undef;
|
||||
return { validation => 'missing', key => $k } if $s->{schema}{missing} eq 'reject';
|
||||
$_[1]{$k} = ref $s->{schema}{default} eq 'CODE' ? $s->{schema}{default}->() : $s->{schema}{default} // undef;
|
||||
next if exists $s->{schema}{default};
|
||||
}
|
||||
|
||||
my $r = _validate($s, $input->{$k});
|
||||
$input->{$k} = $r->[0];
|
||||
if($r->[1]) {
|
||||
$r->[1]{key} = $k;
|
||||
push @err, $r->[1];
|
||||
my $r = _validate($s, $_[1]{$k});
|
||||
if ($r) {
|
||||
$r->{key} = $k;
|
||||
push @err, $r;
|
||||
}
|
||||
}
|
||||
return [$input, { validation => 'keys', errors => \@err }] if @err;
|
||||
return { validation => 'keys', errors => \@err } if @err;
|
||||
}
|
||||
|
||||
# array values
|
||||
if($c->{schema}{values}) {
|
||||
if ($c->{schema}{values}) {
|
||||
my @err;
|
||||
for my $i (0..$#$input) {
|
||||
my $r = _validate($c->{schema}{values}, $input->[$i]);
|
||||
$input->[$i] = $r->[0];
|
||||
if($r->[1]) {
|
||||
$r->[1]{index} = $i;
|
||||
push @err, $r->[1];
|
||||
for my $i (0..$#{$_[1]}) {
|
||||
my $r = _validate($c->{schema}{values}, $_[1][$i]);
|
||||
if ($r) {
|
||||
$r->{index} = $i;
|
||||
push @err, $r;
|
||||
}
|
||||
}
|
||||
return [$input, { validation => 'values', errors => \@err }] if @err;
|
||||
return { validation => 'values', errors => \@err } if @err;
|
||||
}
|
||||
|
||||
# validations
|
||||
for ($c->{validations}->@*) {
|
||||
my $r = _validate_rec($_, $input);
|
||||
$input = $r->[0];
|
||||
|
||||
return [$input, {
|
||||
my $r = _validate_rec($_, $_[1]);
|
||||
return {
|
||||
# If the error was a custom 'func' object, then make that the primary cause.
|
||||
# This makes it possible for validations to provide their own error objects.
|
||||
$r->[1]{validation} eq 'func' && (!exists $r->[1]{result} || keys $r->[1]->%* > 2) ? $r->[1]->%* : (error => $r->[1]),
|
||||
$r->{validation} eq 'func' && (!exists $r->{result} || keys $r->%* > 2) ? $r->%* : (error => $r),
|
||||
validation => $_->{name},
|
||||
}] if $r->[1];
|
||||
} if $r;
|
||||
}
|
||||
|
||||
# func
|
||||
if($c->{schema}{func}) {
|
||||
my $r = $c->{schema}{func}->($input);
|
||||
return [$input, { %$r, validation => 'func' }] if ref $r eq 'HASH';
|
||||
return [$input, { validation => 'func', result => $r }] if !$r;
|
||||
if ($c->{schema}{func}) {
|
||||
my $r = $c->{schema}{func}->($_[1]);
|
||||
return { %$r, validation => 'func' } if ref $r eq 'HASH';
|
||||
return { validation => 'func', result => $r } if !$r;
|
||||
}
|
||||
|
||||
return [$input]
|
||||
}
|
||||
|
||||
|
||||
sub _validate_array($c, $input) {
|
||||
return [$input] if $c->{schema}{type} ne 'array';
|
||||
sub _validate_array {
|
||||
my $c = $_[0];
|
||||
return if $c->{schema}{type} ne 'array';
|
||||
|
||||
$input = [sort { $c->{schema}{sort}->($a, $b) } @$input ] if $c->{schema}{sort};
|
||||
$_[1] = [sort { $c->{schema}{sort}->($a, $b) } $_[1]->@* ] if $c->{schema}{sort};
|
||||
|
||||
# Key-based uniqueness
|
||||
if($c->{schema}{unique} && ref $c->{schema}{unique} eq 'CODE') {
|
||||
if ($c->{schema}{unique} && ref $c->{schema}{unique} eq 'CODE') {
|
||||
my %h;
|
||||
for my $i (0..$#$input) {
|
||||
my $k = $c->{schema}{unique}->($input->[$i]);
|
||||
return [$input, { validation => 'unique', index_a => $h{$k}, value_a => $input->[$h{$k}], index_b => $i, value_b => $input->[$i], key => $k }] if exists $h{$k};
|
||||
for my $i (0..$#{$_[1]}) {
|
||||
my $k = $c->{schema}{unique}->($_[1][$i]);
|
||||
return { validation => 'unique', index_a => $h{$k}, value_a => $_[1][$h{$k}], index_b => $i, value_b => $_[1][$i], key => $k } if exists $h{$k};
|
||||
$h{$k} = $i;
|
||||
}
|
||||
|
||||
# Comparison-based uniqueness
|
||||
} elsif($c->{schema}{unique}) {
|
||||
for my $i (0..$#$input-1) {
|
||||
return [$input, { validation => 'unique', index_a => $i, value_a => $input->[$i], index_b => $i+1, value_b => $input->[$i+1] }]
|
||||
if $c->{schema}{sort}->($input->[$i], $input->[$i+1]) == 0
|
||||
} elsif ($c->{schema}{unique}) {
|
||||
for my $i (0..$#{$_[1]}-1) {
|
||||
return { validation => 'unique', index_a => $i, value_a => $_[1][$i], index_b => $i+1, value_b => $_[1][$i+1] }
|
||||
if $c->{schema}{sort}->($_[1][$i], $_[1][$i+1]) == 0
|
||||
}
|
||||
}
|
||||
|
||||
return [$input]
|
||||
}
|
||||
|
||||
|
||||
sub _validate_input($c, $input) {
|
||||
sub _validate_input {
|
||||
my $c = $_[0];
|
||||
|
||||
# rmwhitespace (needs to be done before the 'default' test)
|
||||
if(defined $input && !ref $input && $c->{schema}{type} eq 'scalar' && $c->{schema}{rmwhitespace}) {
|
||||
$input =~ s/\r//g;
|
||||
$input =~ s/^\s*//;
|
||||
$input =~ s/\s*$//;
|
||||
if (defined $_[1] && !ref $_[1] && $c->{schema}{type} eq 'scalar' && $c->{schema}{rmwhitespace}) {
|
||||
$_[1] =~ s/\r//g;
|
||||
$_[1] =~ s/^\s*//;
|
||||
$_[1] =~ s/\s*$//;
|
||||
}
|
||||
|
||||
# default
|
||||
if(!defined $input || (!ref $input && $input eq '')) {
|
||||
return [ref $c->{schema}{default} eq 'CODE' ? $c->{schema}{default}->($input) : $c->{schema}{default}] if exists $c->{schema}{default};
|
||||
return [$input, { validation => 'required' }];
|
||||
if (!defined $_[1] || (!ref $_[1] && $_[1] eq '')) {
|
||||
if (exists $c->{schema}{default}) {
|
||||
$_[1] = ref $c->{schema}{default} eq 'CODE' ? $c->{schema}{default}->($_[1]) : $c->{schema}{default};
|
||||
return;
|
||||
}
|
||||
return { validation => 'required' };
|
||||
}
|
||||
|
||||
if($c->{schema}{type} eq 'scalar') {
|
||||
return [$input, { validation => 'type', expected => 'scalar', got => lc ref $input }] if ref $input;
|
||||
if ($c->{schema}{type} eq 'scalar') {
|
||||
return { validation => 'type', expected => 'scalar', got => lc ref $_[1] } if ref $_[1];
|
||||
|
||||
} elsif($c->{schema}{type} eq 'hash') {
|
||||
return [$input, { validation => 'type', expected => 'hash', got => lc ref $input || 'scalar' }] if ref $input ne 'HASH';
|
||||
} elsif ($c->{schema}{type} eq 'hash') {
|
||||
return { validation => 'type', expected => 'hash', got => lc ref $_[1] || 'scalar' } if ref $_[1] ne 'HASH';
|
||||
|
||||
# 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->{schema}{unknown} eq 'remove') {
|
||||
$input = { map +($_, $input->{$_}), grep $c->{known_keys}{$_}, keys %$input };
|
||||
} elsif($c->{schema}{unknown} eq 'reject') {
|
||||
my @err = grep !$c->{known_keys}{$_}, keys %$input;
|
||||
return [$input, { validation => 'unknown', keys => \@err, expected => [ sort keys %{$c->{known_keys}} ] }] if @err;
|
||||
$input = { %$input };
|
||||
if ($c->{schema}{unknown} eq 'remove') {
|
||||
$_[1] = { map +($_, $_[1]{$_}), grep $c->{known_keys}{$_}, keys $_[1]->%* };
|
||||
} elsif ($c->{schema}{unknown} eq 'reject') {
|
||||
my @err = grep !$c->{known_keys}{$_}, keys $_[1]->%*;
|
||||
return { validation => 'unknown', keys => \@err, expected => [ sort keys %{$c->{known_keys}} ] } if @err;
|
||||
$_[1] = { $_[1]->%* };
|
||||
} else {
|
||||
$input = { %$input };
|
||||
$_[1] = { $_[1]->%* };
|
||||
}
|
||||
|
||||
} elsif($c->{schema}{type} eq 'array') {
|
||||
$input = [$input] if $c->{schema}{scalar} && !ref $input;
|
||||
return [$input, { validation => 'type', expected => $c->{schema}{scalar} ? 'array or scalar' : 'array', got => lc ref $input || 'scalar' }] if ref $input ne 'ARRAY';
|
||||
$input = [@$input]; # Create a shallow copy to prevent in-place modification.
|
||||
} elsif ($c->{schema}{type} eq 'array') {
|
||||
$_[1] = [$_[1]] if $c->{schema}{scalar} && !ref $_[1];
|
||||
return { validation => 'type', expected => $c->{schema}{scalar} ? 'array or scalar' : 'array', got => lc ref $_[1] || 'scalar' } if ref $_[1] ne 'ARRAY';
|
||||
$_[1] = [$_[1]->@*]; # Create a shallow copy to prevent in-place modification.
|
||||
|
||||
} elsif($c->{schema}{type} eq 'any') {
|
||||
} elsif ($c->{schema}{type} eq 'any') {
|
||||
# No need to do anything here.
|
||||
|
||||
} else {
|
||||
confess "Unknown type '$c->{schema}{type}'"; # Already checked in compile(), but be extra safe
|
||||
}
|
||||
|
||||
my $r = _validate_rec($c, $input);
|
||||
return $r if $r->[1];
|
||||
$input = $r->[0];
|
||||
|
||||
_validate_array($c, $input);
|
||||
&_validate_rec || &_validate_array;
|
||||
}
|
||||
|
||||
|
||||
sub _validate($c, $input) {
|
||||
my $r = _validate_input($c, $input);
|
||||
return $r if !$r->[1] || !exists $c->{schema}{onerror};
|
||||
[ ref $c->{schema}{onerror} eq 'CODE' ? $c->{schema}{onerror}->(bless $r, 'FU::Validate::Result') : $c->{schema}{onerror} ]
|
||||
sub _validate {
|
||||
my $c = $_[0];
|
||||
my $r = &_validate_input;
|
||||
($r, $_[1]) = (undef, ref $c->{schema}{onerror} eq 'CODE' ? $c->{schema}{onerror}->($_[0], bless $r, 'FU::Validate::err') : $c->{schema}{onerror})
|
||||
if $r && exists $c->{schema}{onerror};
|
||||
$r
|
||||
}
|
||||
|
||||
|
||||
sub validate($c, $input) {
|
||||
bless _validate($c, $input), 'FU::Validate::Result';
|
||||
my $r = _validate($c, $input);
|
||||
return $input if !$r;
|
||||
die bless $r, 'FU::Validate::err';
|
||||
$input
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
package FU::Validate::Result;
|
||||
|
||||
package FU::Validate::err;
|
||||
use v5.36;
|
||||
use Carp 'confess';
|
||||
|
||||
# A result object contains: [$data, $error]
|
||||
use overload '""' => sub {
|
||||
# TODO: Better error message
|
||||
require Data::Dumper;
|
||||
Data::Dumper->new([{$_[0]->%*}])->Terse(1)->Pair(':')->Indent(0)->Sortkeys(1)->Dump."\n";
|
||||
};
|
||||
|
||||
# In boolean context, returns whether the validation succeeded.
|
||||
use overload bool => sub { !$_[0][1] };
|
||||
|
||||
# Returns the validation errors, or undef if validation succeeded
|
||||
sub err { $_[0][1] }
|
||||
|
||||
# Returns the validated and normalized input, dies if validation didn't succeed.
|
||||
sub data {
|
||||
if($_[0][1]) {
|
||||
require Data::Dumper;
|
||||
my $s = Data::Dumper->new([$_[0][1]])->Terse(1)->Pair(':')->Indent(0)->Sortkeys(1)->Dump;
|
||||
confess "Validation failed: $s";
|
||||
}
|
||||
$_[0][0]
|
||||
}
|
||||
|
||||
# Same as 'data', but returns partially validated and normalized data if validation failed.
|
||||
sub unsafe_data { $_[0][0] }
|
||||
|
||||
# TODO: Human-readable error message formatting
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
|
@ -402,67 +385,22 @@ follows:
|
|||
|
||||
C<$schema> is the schema that describes the data to be validated (see L</SCHEMA
|
||||
DEFINITION> below) and C<$validations> is an optional hashref containing
|
||||
L<custom validations|/Custom validations> that C<$schema> can refer to.
|
||||
L<custom validations|/Custom validations> that C<$schema> can refer to. An
|
||||
error is thrown if the C<$validations> or C<$schema> are invalid.
|
||||
|
||||
To validate input, run:
|
||||
|
||||
my $result = $validator->validate($input);
|
||||
my $validated_input = $validator->validate($input);
|
||||
|
||||
C<$input> is the data to be validated, and the C<$result> object is L<described
|
||||
below|/Result object>.
|
||||
C<validate()> returns a validated and (depending on the schema) normalized copy
|
||||
of C<$input>. Great care is taken that C<$input> is not being modified
|
||||
in-place, even if data normalization is being performed.
|
||||
|
||||
Both C<compile()> and C<validate()> may throw an error if the C<$validations>
|
||||
or C<$schema> are invalid. Errors in the C<$input> should never cause an error
|
||||
to be thrown, since these are always reported in the C<$result> object.
|
||||
|
||||
This module takes great care that C<$input> is not being modified in place,
|
||||
even if data normalization is being performed. The normalized data can be read
|
||||
from the C<$result> object.
|
||||
|
||||
=head2 Result object
|
||||
|
||||
The C<$result> object returned by C<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:
|
||||
|
||||
=over
|
||||
|
||||
=item 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 I<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 C<$input>, but may in some cases still have nested
|
||||
references to data in C<$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 C<$input>. Make a deep copy of the data if you're concerned about
|
||||
this).
|
||||
|
||||
=item err()
|
||||
|
||||
Returns I<undef> if validation succeeded, an error object otherwise.
|
||||
|
||||
An error object is a hashref containing at least one key: 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.
|
||||
|
||||
=back
|
||||
An error is thrown if the input does not validate. The error object is a
|
||||
C<FU::Validate::err>-blessed hashref containing at least one key:
|
||||
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.
|
||||
|
||||
|
||||
=head1 SCHEMA DEFINITION
|
||||
|
|
@ -519,9 +457,9 @@ Instead of reporting an error, return C<$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 C<$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.
|
||||
If C<$val> is a CODE reference, the subroutine is called with the (partially
|
||||
normalized) input as first argument and error object as second argument. The
|
||||
return value of the subroutine is then returned for this validation.
|
||||
|
||||
=item rmwhitespace => 0/1
|
||||
|
||||
|
|
@ -856,11 +794,11 @@ Here's a simple example that defines and uses a custom validation named
|
|||
I<stringbool>, which accepts either the string I<true> or I<false>.
|
||||
|
||||
my $validations = {
|
||||
stringbool => { enum => ['true', 'false'] }
|
||||
stringbool => { enum => ['true', 'false'] }
|
||||
};
|
||||
my $schema = { stringbool => 1 };
|
||||
my $result = FU::Validate->compile($schema, $validations)->validate('true');
|
||||
# $result->data() eq 'true'
|
||||
# $result 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 I<prefix> custom validation, which
|
||||
|
|
@ -868,9 +806,9 @@ requires that the string starts with the given prefix. The subroutine returns a
|
|||
schema that contains the I<func> built-in option to do the actual validation.
|
||||
|
||||
my $validations = {
|
||||
prefix => sub($prefix) {
|
||||
return { func => sub { $_[0] =~ /^\Q$prefix/ } }
|
||||
}
|
||||
prefix => sub($prefix) {
|
||||
return { func => sub { $_[0] =~ /^\Q$prefix/ } }
|
||||
}
|
||||
};
|
||||
my $schema = { prefix => 'Hello, ' };
|
||||
my $result = FU::Validate->compile($schema, $validations)->validate('Hello, World!');
|
||||
|
|
@ -891,10 +829,10 @@ 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
|
||||
# top-level schema says we expect a hash
|
||||
type => 'hash',
|
||||
# but the 'int' validation implies that the type is a scalar
|
||||
int => 1
|
||||
});
|
||||
|
||||
The I<keys>, I<values> and C<func> built-in options are validated separately
|
||||
|
|
|
|||
353
t/validate.t
353
t/validate.t
|
|
@ -14,7 +14,7 @@ my %validations = (
|
|||
setundef => { func => sub { $_[0] = undef; 1 } },
|
||||
defaultsub1 => { default => sub { 2 } },
|
||||
defaultsub2 => { default => sub { defined $_[0] } },
|
||||
onerrorsub => { onerror => sub { ref $_[0] } },
|
||||
onerrorsub => { onerror => sub { ref $_[1] } },
|
||||
collapsews => { rmwhitespace => 0, func => sub { $_[0] =~ s/\s+/ /g; 1 } },
|
||||
neverfails => { onerror => 'err' },
|
||||
revnum => { type => 'array', sort => sub($x,$y) { $y <=> $x } },
|
||||
|
|
@ -31,8 +31,7 @@ my %validations = (
|
|||
);
|
||||
|
||||
|
||||
sub t {
|
||||
my($schema, $input, $output, $error) = @_;
|
||||
sub t($schema, $input, $output) {
|
||||
my $line = (caller)[2];
|
||||
|
||||
my $schema_copy = dclone([$schema])->[0];
|
||||
|
|
@ -40,203 +39,215 @@ sub t {
|
|||
|
||||
my $res = FU::Validate->compile($schema, \%validations)->validate($input);
|
||||
#diag explain FU::Validate->compile($schema, \%validations) if $line == 139;
|
||||
is !$error, !!$res, "boolean context $line";
|
||||
is_deeply $schema, $schema_copy, "schema modification $line";
|
||||
is_deeply $input, $input_copy, "input modification $line";
|
||||
is_deeply $res->unsafe_data(), $output, "unsafe_data $line";
|
||||
is_deeply $res->data(), $output, "data ok $line" if !$error;
|
||||
ok !eval { $res->data; 1}, "data err $line" if $error;
|
||||
is_deeply $res->err(), $error, "err $line";
|
||||
is_deeply $res, $output, "data ok $line";
|
||||
}
|
||||
|
||||
sub f($schema, $input, $error) {
|
||||
my $line = (caller)[2];
|
||||
|
||||
my $schema_copy = dclone([$schema])->[0];
|
||||
my $input_copy = dclone([$input])->[0];
|
||||
|
||||
ok !eval { FU::Validate->compile($schema, \%validations)->validate($input); 1 }, "eval $line";
|
||||
is_deeply $schema, $schema_copy, "schema modification $line";
|
||||
is_deeply $input, $input_copy, "input modification $line";
|
||||
is_deeply { $@->%* }, $error, "err $line";
|
||||
}
|
||||
|
||||
|
||||
# default
|
||||
t {}, 0, 0, undef;
|
||||
t {}, '', '', { validation => 'required' };
|
||||
t {}, undef, undef, { validation => 'required' };
|
||||
t { default => undef }, undef, undef, undef;
|
||||
t { default => undef }, '', undef, undef;
|
||||
t { defaultsub1 => 1 }, undef, 2, undef;
|
||||
t { defaultsub2 => 1 }, undef, '', undef;
|
||||
t { defaultsub2 => 1 }, '', 1, undef;
|
||||
t { onerrorsub => 1 }, undef, 'FU::Validate::Result', undef;
|
||||
t {}, 0, 0;
|
||||
f {}, '', { validation => 'required' };
|
||||
f {}, undef, { validation => 'required' };
|
||||
t { default => undef }, undef, undef;
|
||||
t { default => undef }, '', undef;
|
||||
t { defaultsub1 => 1 }, undef, 2;
|
||||
t { defaultsub2 => 1 }, undef, '';
|
||||
t { defaultsub2 => 1 }, '', 1;
|
||||
t { onerrorsub => 1 }, undef, 'FU::Validate::err';
|
||||
|
||||
# rmwhitespace
|
||||
t {}, " Va\rl id \n ", 'Val id', undef;
|
||||
t { rmwhitespace => 0 }, " Va\rl id \n ", " Va\rl id \n ", undef;
|
||||
t {}, ' ', '', { validation => 'required' };
|
||||
t { rmwhitespace => 0 }, ' ', ' ', undef;
|
||||
t {}, " Va\rl id \n ", 'Val id';
|
||||
t { rmwhitespace => 0 }, " Va\rl id \n ", " Va\rl id \n ";
|
||||
f {}, ' ', { validation => 'required' };
|
||||
t { rmwhitespace => 0 }, ' ', ' ';
|
||||
|
||||
# arrays
|
||||
t {}, [], [], { validation => 'type', expected => 'scalar', got => 'array' };
|
||||
t { type => 'array' }, 1, 1, { validation => 'type', expected => 'array', got => 'scalar' };
|
||||
t { type => 'array' }, [], [], undef;
|
||||
t { type => 'array' }, [undef,1,2,{}], [undef,1,2,{}], undef;
|
||||
t { type => 'array', scalar => 1 }, 1, [1], undef;
|
||||
t { type => 'array', values => {} }, [undef], [undef], { validation => 'values', errors => [{ index => 0, validation => 'required' }] };
|
||||
t { type => 'array', values => {} }, [' a '], ['a'], undef;
|
||||
t { type => 'array', sort => 'str' }, [qw/20 100 3/], [qw/100 20 3/], undef;
|
||||
t { type => 'array', sort => 'num' }, [qw/20 100 3/], [qw/3 20 100/], undef;
|
||||
t { revnum => 1 }, [qw/20 100 3/], [qw/100 20 3/], undef;
|
||||
t { type => 'array', sort => 'num', unique => 1 }, [qw/3 2 1/], [qw/1 2 3/], undef;
|
||||
t { type => 'array', sort => 'num', unique => 1 }, [qw/3 2 3/], [qw/2 3 3/], { validation => 'unique', index_a => 1, value_a => 3, index_b => 2, value_b => 3 };
|
||||
t { type => 'array', unique => 1 }, [qw/3 1 2/], [qw/3 1 2/], undef;
|
||||
t { type => 'array', unique => 1 }, [qw/3 1 3/], [qw/3 1 3/], { validation => 'unique', index_a => 0, value_a => 3, index_b => 2, value_b => 3, key => 3 };
|
||||
t { uniquelength => 1 }, [[],[1],[1,2]], [[],[1],[1,2]], undef;
|
||||
t { uniquelength => 1 }, [[],[1],[2]], [[],[1],[2]], { validation => 'unique', index_a => 1, value_a => [1], index_b => 2, value_b => [2], key => 1 };
|
||||
t { type => 'array', setundef => 1 }, [], undef, undef;
|
||||
t { type => 'array', values => { type => 'any', setundef => 1 } }, [[]], [undef], undef;
|
||||
f {}, [], { validation => 'type', expected => 'scalar', got => 'array' };
|
||||
f { type => 'array' }, 1, { validation => 'type', expected => 'array', got => 'scalar' };
|
||||
t { type => 'array' }, [], [];
|
||||
t { type => 'array' }, [undef,1,2,{}], [undef,1,2,{}];
|
||||
t { type => 'array', scalar => 1 }, 1, [1];
|
||||
f { type => 'array', values => {} }, [undef], { validation => 'values', errors => [{ index => 0, validation => 'required' }] };
|
||||
t { type => 'array', values => {} }, [' a '], ['a'];
|
||||
t { type => 'array', sort => 'str' }, [qw/20 100 3/], [qw/100 20 3/];
|
||||
t { type => 'array', sort => 'num' }, [qw/20 100 3/], [qw/3 20 100/];
|
||||
t { revnum => 1 }, [qw/20 100 3/], [qw/100 20 3/];
|
||||
t { type => 'array', sort => 'num', unique => 1 }, [qw/3 2 1/], [qw/1 2 3/];
|
||||
f { type => 'array', sort => 'num', unique => 1 }, [qw/3 2 3/], { validation => 'unique', index_a => 1, value_a => 3, index_b => 2, value_b => 3 };
|
||||
t { type => 'array', unique => 1 }, [qw/3 1 2/], [qw/3 1 2/];
|
||||
f { type => 'array', unique => 1 }, [qw/3 1 3/], { validation => 'unique', index_a => 0, value_a => 3, index_b => 2, value_b => 3, key => 3 };
|
||||
t { uniquelength => 1 }, [[],[1],[1,2]], [[],[1],[1,2]];
|
||||
f { uniquelength => 1 }, [[],[1],[2]], { validation => 'unique', index_a => 1, value_a => [1], index_b => 2, value_b => [2], key => 1 };
|
||||
t { type => 'array', setundef => 1 }, [], undef;
|
||||
t { type => 'array', values => { type => 'any', setundef => 1 } }, [[]], [undef];
|
||||
|
||||
# hashes
|
||||
t { type => 'hash' }, [], [], { validation => 'type', expected => 'hash', got => 'array' };
|
||||
t { type => 'hash' }, 'a', 'a', { validation => 'type', expected => 'hash', got => 'scalar' };
|
||||
t { type => 'hash' }, {a=>[],b=>undef,c=>{}}, {}, undef;
|
||||
t { type => 'hash', keys => { a=>{} } }, {}, {a=>undef}, { validation => 'keys', errors => [{ key => 'a', validation => 'required' }] }; # XXX: the key doesn't necessarily have to be created
|
||||
t { type => 'hash', keys => { a=>{missing=>'ignore'} } }, {}, {}, undef;
|
||||
t { type => 'hash', keys => { a=>{default=>undef} } }, {}, {a=>undef}, undef;
|
||||
t { type => 'hash', keys => { a=>{missing=>'create',default=>undef} } }, {}, {a=>undef}, undef;
|
||||
t { type => 'hash', keys => { a=>{missing=>'reject'} } }, {}, {}, {key => 'a', validation => 'missing'};
|
||||
f { type => 'hash' }, [], { validation => 'type', expected => 'hash', got => 'array' };
|
||||
f { type => 'hash' }, 'a', { validation => 'type', expected => 'hash', got => 'scalar' };
|
||||
t { type => 'hash' }, {a=>[],b=>undef,c=>{}}, {};
|
||||
f { type => 'hash', keys => { a=>{} } }, {}, { validation => 'keys', errors => [{ key => 'a', validation => 'required' }] };
|
||||
t { type => 'hash', keys => { a=>{missing=>'ignore'} } }, {}, {};
|
||||
t { type => 'hash', keys => { a=>{default=>undef} } }, {}, {a=>undef};
|
||||
t { type => 'hash', keys => { a=>{missing=>'create',default=>undef} } }, {}, {a=>undef};
|
||||
f { type => 'hash', keys => { a=>{missing=>'reject'} } }, {}, {key => 'a', validation => 'missing'};
|
||||
|
||||
t { type => 'hash', keys => { a=>{} } }, {a=>' a '}, {a=>'a'}, undef; # Test against in-place modification
|
||||
t { type => 'hash', keys => { a=>{} }, unknown => 'remove' }, { a=>1,b=>1 }, { a=>1 }, undef;
|
||||
t { type => 'hash', keys => { a=>{} }, unknown => 'reject' }, { a=>1,b=>1 }, { a=>1,b=>1 }, { validation => 'unknown', keys => ['b'], expected => ['a'] };
|
||||
t { type => 'hash', keys => { a=>{} }, unknown => 'pass' }, { a=>1,b=>1 }, { a=>1,b=>1 }, undef;
|
||||
t { type => 'hash', setundef => 1 }, {}, undef, undef;
|
||||
t { type => 'hash', unknown => 'reject', keys => { a=>{ type => 'any', setundef => 1}} }, {a=>[]}, {a=>undef}, undef;
|
||||
t { type => 'hash', keys => { a=>{} } }, {a=>' a '}, {a=>'a'}; # Test against in-place modification
|
||||
t { type => 'hash', keys => { a=>{} }, unknown => 'remove' }, { a=>1,b=>1 }, { a=>1 };
|
||||
f { type => 'hash', keys => { a=>{} }, unknown => 'reject' }, { a=>1,b=>1 }, { validation => 'unknown', keys => ['b'], expected => ['a'] };
|
||||
t { type => 'hash', keys => { a=>{} }, unknown => 'pass' }, { a=>1,b=>1 }, { a=>1,b=>1 };
|
||||
t { type => 'hash', setundef => 1 }, {}, undef;
|
||||
t { type => 'hash', unknown => 'reject', keys => { a=>{ type => 'any', setundef => 1}} }, {a=>[]}, {a=>undef};
|
||||
|
||||
# default validations
|
||||
t { minlength => 3 }, 'ab', 'ab', { validation => 'minlength', expected => 3, got => 2 };
|
||||
t { minlength => 3 }, 'abc', 'abc', undef;
|
||||
t { maxlength => 3 }, 'abcd', 'abcd', { validation => 'maxlength', expected => 3, got => 4 };
|
||||
t { maxlength => 3 }, 'abc', 'abc', undef;
|
||||
t { minlength => 3, maxlength => 3 }, 'abc', 'abc', undef;
|
||||
t { length => 3 }, 'ab', 'ab', { validation => 'length', expected => 3, got => 2 };
|
||||
t { length => 3 }, 'abcd', 'abcd', { validation => 'length', expected => 3, got => 4 };
|
||||
t { length => 3 }, 'abc', 'abc', undef;
|
||||
t { length => [1,3] }, 'abc', 'abc', undef;
|
||||
t { length => [1,3] }, 'abcd', 'abcd', { validation => 'length', expected => [1,3], got => 4 };;
|
||||
t { type => 'array', length => 0 }, [], [], undef;
|
||||
t { type => 'array', length => 1 }, [1,2], [1,2], { validation => 'length', expected => 1, got => 2 };
|
||||
t { type => 'hash', length => 0 }, {}, {}, undef;
|
||||
t { type => 'hash', length => 1, unknown => 'pass' }, {qw/1 a 2 b/}, {qw/1 a 2 b/}, { validation => 'length', expected => 1, got => 2 };
|
||||
t { type => 'hash', length => 1, keys => {a => {missing=>'ignore'}, b => {missing=>'ignore'}} }, {a=>1}, {a=>1}, undef;
|
||||
t { regex => '^a' }, 'abc', 'abc', undef; # XXX: Can't use qr// here because t() does dclone(). The 'hex' test covers that case anyway.
|
||||
t { regex => '^a' }, 'cba', 'cba', { validation => 'regex', regex => '^a', got => 'cba' };
|
||||
t { enum => [1,2] }, 1, 1, undef;
|
||||
t { enum => [1,2] }, 2, 2, undef;
|
||||
t { enum => [1,2] }, 3, 3, { validation => 'enum', expected => [1,2], got => 3 };
|
||||
t { enum => 1 }, 1, 1, undef;
|
||||
t { enum => 1 }, 2, 2, { validation => 'enum', expected => [1], got => 2 };
|
||||
t { enum => {a=>1,b=>2} }, 'a', 'a', undef;
|
||||
t { enum => {a=>1,b=>2} }, 'c', 'c', { validation => 'enum', expected => ['a','b'], got => 'c' };
|
||||
t { anybool => 1 }, 1, true, undef;
|
||||
t { anybool => 1 }, undef, false, undef;
|
||||
t { anybool => 1 }, '', false, undef;
|
||||
t { anybool => 1 }, {}, true, undef;
|
||||
t { anybool => 1 }, [], true, undef;
|
||||
t { anybool => 1 }, bless({}, 'test'), true, undef;
|
||||
t { bool => 1 }, 1, 1, { validation => 'bool' };
|
||||
t { bool => 1 }, \1, true, undef;
|
||||
f { minlength => 3 }, 'ab', { validation => 'minlength', expected => 3, got => 2 };
|
||||
t { minlength => 3 }, 'abc', 'abc';
|
||||
f { maxlength => 3 }, 'abcd', { validation => 'maxlength', expected => 3, got => 4 };
|
||||
t { maxlength => 3 }, 'abc', 'abc';
|
||||
t { minlength => 3, maxlength => 3 }, 'abc', 'abc';
|
||||
f { length => 3 }, 'ab', { validation => 'length', expected => 3, got => 2 };
|
||||
f { length => 3 }, 'abcd', { validation => 'length', expected => 3, got => 4 };
|
||||
t { length => 3 }, 'abc', 'abc';
|
||||
t { length => [1,3] }, 'abc', 'abc';
|
||||
f { length => [1,3] }, 'abcd', { validation => 'length', expected => [1,3], got => 4 };;
|
||||
t { type => 'array', length => 0 }, [], [];
|
||||
f { type => 'array', length => 1 }, [1,2], { validation => 'length', expected => 1, got => 2 };
|
||||
t { type => 'hash', length => 0 }, {}, {};
|
||||
f { type => 'hash', length => 1, unknown => 'pass' }, {qw/1 a 2 b/}, { validation => 'length', expected => 1, got => 2 };
|
||||
t { type => 'hash', length => 1, keys => {a => {missing=>'ignore'}, b => {missing=>'ignore'}} }, {a=>1}, {a=>1};
|
||||
t { regex => '^a' }, 'abc', 'abc'; # XXX: Can't use qr// here because t() does dclone(). The 'hex' test covers that case anyway.
|
||||
f { regex => '^a' }, 'cba', { validation => 'regex', regex => '^a', got => 'cba' };
|
||||
t { enum => [1,2] }, 1, 1;
|
||||
t { enum => [1,2] }, 2, 2;
|
||||
f { enum => [1,2] }, 3, { validation => 'enum', expected => [1,2], got => 3 };
|
||||
t { enum => 1 }, 1, 1;
|
||||
f { enum => 1 }, 2, { validation => 'enum', expected => [1], got => 2 };
|
||||
t { enum => {a=>1,b=>2} }, 'a', 'a';
|
||||
f { enum => {a=>1,b=>2} }, 'c', { validation => 'enum', expected => ['a','b'], got => 'c' };
|
||||
t { anybool => 1 }, 1, true;
|
||||
t { anybool => 1 }, undef, false;
|
||||
t { anybool => 1 }, '', false;
|
||||
t { anybool => 1 }, {}, true;
|
||||
t { anybool => 1 }, [], true;
|
||||
t { anybool => 1 }, bless({}, 'test'), true;
|
||||
f { bool => 1 }, 1, { validation => 'bool' };
|
||||
t { bool => 1 }, \1, true;
|
||||
my($true, $false) = (1,0);
|
||||
t { bool => 1 }, bless(\$true, 'boolean'), true, undef;
|
||||
t { bool => 1 }, bless(\$false, 'boolean'), false, undef;
|
||||
t { bool => 1 }, bless(\$true, 'test'), bless(\$true, 'test'), { validation => 'bool' };
|
||||
t { ascii => 1 }, 'ab c', 'ab c', undef;
|
||||
t { ascii => 1 }, "a\nb", "a\nb", { validation => 'ascii', got => "a\nb" };
|
||||
t { bool => 1 }, bless(\$true, 'boolean'), true;
|
||||
t { bool => 1 }, bless(\$false, 'boolean'), false;
|
||||
f { bool => 1 }, bless(\$true, 'test'), { validation => 'bool' };
|
||||
t { ascii => 1 }, 'ab c', 'ab c';
|
||||
f { ascii => 1 }, "a\nb", { validation => 'ascii', got => "a\nb" };
|
||||
|
||||
# custom validations
|
||||
t { hex => 1 }, 'DeadBeef', 'DeadBeef', undef;
|
||||
t { hex => 1 }, 'x', 'x', { validation => 'hex', error => { validation => 'regex', regex => "$validations{hex}{regex}", got => 'x' } };
|
||||
t { prefix => 'a' }, 'abc', 'abc', undef;
|
||||
t { prefix => 'a' }, 'cba', 'cba', { validation => 'prefix', error => { validation => 'func', result => '' } };
|
||||
t { mybool => 1 }, 'abc', 1, undef;
|
||||
t { mybool => 1 }, undef, 0, undef;
|
||||
t { mybool => 1 }, '', 0, undef;
|
||||
t { collapsews => 1 }, " \t\n ", ' ', undef;
|
||||
t { collapsews => 1 }, ' x ', ' x ', undef;
|
||||
t { collapsews => 1, rmwhitespace => 1 }, ' x ', 'x', undef;
|
||||
t { person => 1 }, 1, 1, { validation => 'type', expected => 'hash', got => 'scalar' };
|
||||
t { person => 1, default => 1 }, undef, 1, undef;
|
||||
t { person => 1 }, { sex => 1 }, { sex => 1, name => undef }, { validation => 'person', error => { validation => 'keys', errors => [{ key => 'name', validation => 'required' }] } };
|
||||
t { person => 1 }, { sex => undef, name => 'y' }, { sex => 1, name => 'y' }, undef;
|
||||
t { person => 1, keys => {age => {default => \'required'}} }, {name => 'x', sex => 'y'}, { name => 'x', sex => 'y', age => undef }, { validation => 'keys', errors => [{ key => 'age', validation => 'required' }] };
|
||||
t { person => 1, keys => {extra => {}} }, {name => 'x', sex => 'y', extra => 1}, { name => 'x', sex => 'y', extra => 1 }, undef;
|
||||
t { person => 1, keys => {extra => {}} }, {name => 'x', sex => 'y', extra => ''}, { name => 'x', sex => 'y', extra => '' }, { validation => 'keys', errors => [{ key => 'extra', validation => 'required' }] };
|
||||
t { person => 1 }, {name => 'x', sex => 'y', extra => 1}, {name => 'x', sex => 'y', extra => 1}, undef;
|
||||
t { person => 1, unknown => 'remove' }, {name => 'x', sex => 'y', extra => 1}, {name => 'x', sex => 'y'}, undef;
|
||||
t { neverfails => 1, int => 1 }, undef, 'err', undef;
|
||||
t { neverfails => 1, int => 1 }, 'x', 'err', undef;
|
||||
t { neverfails => 1, int => 1, onerror => undef }, 'x', undef, undef; # XXX: no way to 'unset' an inherited onerror clause, hmm.
|
||||
t { hex => 1 }, 'DeadBeef', 'DeadBeef';
|
||||
f { hex => 1 }, 'x', { validation => 'hex', error => { validation => 'regex', regex => "$validations{hex}{regex}", got => 'x' } };
|
||||
t { prefix => 'a' }, 'abc', 'abc';
|
||||
f { prefix => 'a' }, 'cba', { validation => 'prefix', error => { validation => 'func', result => '' } };
|
||||
t { mybool => 1 }, 'abc', 1;
|
||||
t { mybool => 1 }, undef, 0;
|
||||
t { mybool => 1 }, '', 0;
|
||||
t { collapsews => 1 }, " \t\n ", ' ';
|
||||
t { collapsews => 1 }, ' x ', ' x ';
|
||||
t { collapsews => 1, rmwhitespace => 1 }, ' x ', 'x';
|
||||
f { person => 1 }, 1, { validation => 'type', expected => 'hash', got => 'scalar' };
|
||||
t { person => 1, default => 1 }, undef, 1;
|
||||
f { person => 1 }, { sex => 1 }, { validation => 'person', error => { validation => 'keys', errors => [{ key => 'name', validation => 'required' }] } };
|
||||
t { person => 1 }, { sex => undef, name => 'y' }, { sex => 1, name => 'y' };
|
||||
f { person => 1, keys => {age => {default => \'required'}} }, {name => 'x', sex => 'y'}, { validation => 'keys', errors => [{ key => 'age', validation => 'required' }] };
|
||||
t { person => 1, keys => {extra => {}} }, {name => 'x', sex => 'y', extra => 1}, { name => 'x', sex => 'y', extra => 1 };
|
||||
f { person => 1, keys => {extra => {}} }, {name => 'x', sex => 'y', extra => ''}, { validation => 'keys', errors => [{ key => 'extra', validation => 'required' }] };
|
||||
t { person => 1 }, {name => 'x', sex => 'y', extra => 1}, {name => 'x', sex => 'y', extra => 1};
|
||||
t { person => 1, unknown => 'remove' }, {name => 'x', sex => 'y', extra => 1}, {name => 'x', sex => 'y'};
|
||||
t { neverfails => 1, int => 1 }, undef, 'err';
|
||||
t { neverfails => 1, int => 1 }, 'x', 'err';
|
||||
t { neverfails => 1, int => 1, onerror => undef }, 'x', undef; # XXX: no way to 'unset' an inherited onerror clause, hmm.
|
||||
|
||||
# numbers
|
||||
sub nerr { +{ validation => 'num', got => $_[0] } }
|
||||
t { num => 1 }, 0, 0, undef;
|
||||
t { num => 1 }, '-', '-', nerr '-';
|
||||
t { num => 1 }, '00', '00', nerr '00';
|
||||
t { num => 1 }, '1', '1', undef;
|
||||
t { num => 1 }, '1.1.', '1.1.', nerr '1.1.';
|
||||
t { num => 1 }, '1.-1', '1.-1', nerr '1.-1';
|
||||
t { num => 1 }, '.1', '.1', nerr '.1';
|
||||
t { num => 1 }, '0.1e5', '0.1e5', undef;
|
||||
t { num => 1 }, '0.1e+5', '0.1e+5', undef;
|
||||
t { num => 1 }, '0.1e5.1', '0.1e5.1', nerr '0.1e5.1';
|
||||
t { int => 1 }, 0, 0, undef;
|
||||
t { int => 1 }, -123, -123, undef;
|
||||
t { int => 1 }, -123.1, -123.1, { validation => 'int', got => -123.1 };
|
||||
t { uint => 1 }, 0, 0, undef;
|
||||
t { uint => 1 }, 123, 123, undef;
|
||||
t { uint => 1 }, -123, -123, { validation => 'uint', got => -123 };
|
||||
t { min => 1 }, 1, 1, undef;
|
||||
t { min => 1 }, 0.9, 0.9, { validation => 'min', expected => 1, got => 0.9 };
|
||||
t { min => 1 }, 'a', 'a', { validation => 'min', error => nerr 'a' };
|
||||
t { max => 1 }, 1, 1, undef;
|
||||
t { max => 1 }, 1.1, 1.1, { validation => 'max', expected => 1, got => 1.1 };
|
||||
t { max => 1 }, 'a', 'a', { validation => 'max', error => nerr 'a' };
|
||||
t { range => [1,2] }, 1, 1, undef;
|
||||
t { range => [1,2] }, 2, 2, undef;
|
||||
t { range => [1,2] }, 0.9, 0.9, { validation => 'range', error => { validation => 'min', expected => 1, got => 0.9 } };
|
||||
t { range => [1,2] }, 2.1, 2.1, { validation => 'range', error => { validation => 'max', expected => 2, got => 2.1 } };
|
||||
t { num => 1 }, 0, 0;
|
||||
f { num => 1 }, '-', nerr '-';
|
||||
f { num => 1 }, '00', nerr '00';
|
||||
t { num => 1 }, '1', '1';
|
||||
f { num => 1 }, '1.1.', nerr '1.1.';
|
||||
f { num => 1 }, '1.-1', nerr '1.-1';
|
||||
f { num => 1 }, '.1', nerr '.1';
|
||||
t { num => 1 }, '0.1e5', '0.1e5';
|
||||
t { num => 1 }, '0.1e+5', '0.1e+5';
|
||||
f { num => 1 }, '0.1e5.1', nerr '0.1e5.1';
|
||||
t { int => 1 }, 0, 0;
|
||||
t { int => 1 }, -123, -123;
|
||||
f { int => 1 }, -123.1, { validation => 'int', got => -123.1 };
|
||||
t { uint => 1 }, 0, 0;
|
||||
t { uint => 1 }, 123, 123;
|
||||
f { uint => 1 }, -123, { validation => 'uint', got => -123 };
|
||||
t { min => 1 }, 1, 1;
|
||||
f { min => 1 }, 0.9, { validation => 'min', expected => 1, got => 0.9 };
|
||||
f { min => 1 }, 'a', { validation => 'min', error => nerr 'a' };
|
||||
t { max => 1 }, 1, 1;
|
||||
f { max => 1 }, 1.1, { validation => 'max', expected => 1, got => 1.1 };
|
||||
f { max => 1 }, 'a', { validation => 'max', error => nerr 'a' };
|
||||
t { range => [1,2] }, 1, 1;
|
||||
t { range => [1,2] }, 2, 2;
|
||||
f { range => [1,2] }, 0.9, { validation => 'range', error => { validation => 'min', expected => 1, got => 0.9 } };
|
||||
f { range => [1,2] }, 2.1, { validation => 'range', error => { validation => 'max', expected => 2, got => 2.1 } };
|
||||
#t { range => [1,2] }, 'a', 'a', { validation => 'range', error => { validation => 'max', error => nerr 'a' } }; # XXX: Error validation type depends on evaluation order
|
||||
|
||||
# email template
|
||||
use utf8;
|
||||
t { email => 1 }, $_->[1], $_->[1], $_->[0] ? undef : { validation => 'email', got => $_->[1] } for (
|
||||
[ 0, 'abc.com' ],
|
||||
[ 0, 'abc@localhost' ],
|
||||
[ 0, 'abc@10.0.0.' ],
|
||||
[ 0, 'abc@256.0.0.1' ],
|
||||
[ 0, '<whoami>@blicky.net' ],
|
||||
[ 0, 'a @a.com' ],
|
||||
[ 0, 'a"@a.com' ],
|
||||
[ 0, 'a@[:]' ],
|
||||
[ 0, 'a@127.0.0.1' ],
|
||||
[ 0, 'a@[::1]' ],
|
||||
[ 1, 'a@a.com' ],
|
||||
[ 1, 'a@a.com.' ],
|
||||
[ 1, 'é@yörhel.nl' ],
|
||||
[ 1, 'a+_0-c@yorhel.nl' ],
|
||||
[ 1, 'é@x-y_z.example' ],
|
||||
[ 1, 'abc@x-y_z.example' ],
|
||||
f { email => 1 }, $_, { validation => 'email', got => $_ } for (
|
||||
'abc.com',
|
||||
'abc@localhost',
|
||||
'abc@10.0.0.',
|
||||
'abc@256.0.0.1',
|
||||
'<whoami>@blicky.net',
|
||||
'a @a.com',
|
||||
'a"@a.com',
|
||||
'a@[:]',
|
||||
'a@127.0.0.1',
|
||||
'a@[::1]',
|
||||
);
|
||||
t { email => 1 }, $_, $_ for (
|
||||
'a@a.com',
|
||||
'a@a.com.',
|
||||
'é@yörhel.nl',
|
||||
'a+_0-c@yorhel.nl',
|
||||
'é@x-y_z.example',
|
||||
'abc@x-y_z.example',
|
||||
);
|
||||
my $long = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@xxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxx.xxxxx';
|
||||
t { email => 1 }, $long, $long, { validation => 'email', error => { validation => 'maxlength', got => 255, expected => 254 } };
|
||||
f { email => 1 }, $long, { validation => 'email', error => { validation => 'maxlength', got => 255, expected => 254 } };
|
||||
|
||||
# weburl template
|
||||
t { weburl => 1 }, $_->[1], $_->[1], $_->[0] ? undef : { validation => 'weburl', got => $_->[1] } for (
|
||||
[ 0, 'http' ],
|
||||
[ 0, 'http://' ],
|
||||
[ 0, 'http:///' ],
|
||||
[ 0, 'http://x/' ],
|
||||
[ 0, 'http://x/' ],
|
||||
[ 0, 'http://256.0.0.1/' ],
|
||||
[ 0, 'http://blicky.net:050/' ],
|
||||
[ 0, 'ftp//blicky.net/' ],
|
||||
[ 1, 'http://blicky.net/' ],
|
||||
[ 1, 'http://blicky.net:50/' ],
|
||||
[ 1, 'https://blicky.net/' ],
|
||||
[ 1, 'https://[::1]:80/' ],
|
||||
[ 1, 'https://l-n.x_.example.com/' ],
|
||||
[ 1, 'https://blicky.net/?#Who\'d%20ever%22makeaurl_like-this/!idont.know' ],
|
||||
f { weburl => 1 }, $_, { validation => 'weburl', got => $_ } for (
|
||||
'http',
|
||||
'http://',
|
||||
'http:///',
|
||||
'http://x/',
|
||||
'http://x/',
|
||||
'http://256.0.0.1/',
|
||||
'http://blicky.net:050/',
|
||||
'ftp//blicky.net/',
|
||||
);
|
||||
t { weburl => 1}, $_, $_ for (
|
||||
'http://blicky.net/',
|
||||
'http://blicky.net:50/',
|
||||
'https://blicky.net/',
|
||||
'https://[::1]:80/',
|
||||
'https://l-n.x_.example.com/',
|
||||
'https://blicky.net/?#Who\'d%20ever%22makeaurl_like-this/!idont.know',
|
||||
);
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue