Bugzilla – Attachment 154768 Details for
Bug 34430
Add Koha::Validator module
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 34430: Module for validating input
Bug-34430-Module-for-validating-input.patch (text/plain), 30.29 KB, created by
Marcel de Rooy
on 2023-08-25 06:36:54 UTC
(
hide
)
Description:
Bug 34430: Module for validating input
Filename:
MIME Type:
Creator:
Marcel de Rooy
Created:
2023-08-25 06:36:54 UTC
Size:
30.29 KB
patch
obsolete
>From e6142f90224c6d3e08d93bfac6b82e658ce7ef28 Mon Sep 17 00:00:00 2001 >From: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> >Date: Thu, 27 Jul 2023 09:37:39 +0000 >Subject: [PATCH] Bug 34430: Module for validating input >Content-Type: text/plain; charset=utf-8 > >Test plan: >Run t/Koha/Validator.t >--- > Koha/Validator.pm | 342 ++++++++++++++++++++++++++++++++++++++++ > t/Koha/Validator.t | 379 +++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 721 insertions(+) > create mode 100644 Koha/Validator.pm > create mode 100755 t/Koha/Validator.t > >diff --git a/Koha/Validator.pm b/Koha/Validator.pm >new file mode 100644 >index 0000000000..dc072f9dcb >--- /dev/null >+++ b/Koha/Validator.pm >@@ -0,0 +1,342 @@ >+package Koha::Validator; >+ >+# Copyright 2023 Rijksmuseum, Koha development team >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha 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. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+=head1 NAME >+ >+Koha::Validator - Facilitate validation of sets of values >+ >+=head1 SYNOPSIS >+ >+ use Koha::Validator; >+ #TODO >+ >+=head1 DESCRIPTION >+ >+ This modules allows you to load rule sets using predefined rules, >+ regular expressions and validation routines, and pass sets of values >+ in order to apply those rules to them. >+ >+=cut >+ >+use Modern::Perl; >+use Data::Dumper qw/Dumper/; #FIXME >+use List::Util qw(any); >+use Try::Tiny; >+ >+use Koha::Exceptions; >+ >+use constant OPTIONS => qw/ allow_missing_rules break_on_error replace_inline restrict_postfilter throw_exception /; >+ >+our $standard_rules = {}; >+ >+=head1 METHODS >+ >+=head2 new >+ >+ Create validator object. Loads standard rules only. >+ >+=cut >+ >+sub new { >+ my ($class) = @_; >+ my $self = bless { _errors => {}, _rules => {} }, $class; >+ return $self->option( reset => 1 )->load($standard_rules); >+} >+ >+=head2 load >+ >+ $validator->load( $rules ); >+ >+ Load rules. Overwrite existing ones. >+ >+=cut >+ >+sub load { >+ my ( $self, $rules ) = @_; >+ if ( ref($rules) eq 'HASH' ) { >+ $self->{_rules} = { %{ $self->{_rules} }, %$rules }; >+ } else { >+ Koha::Exceptions::BadParameter->throw( parameter => 'rules' ); >+ } >+ return $self->_replace_aliases; >+} >+ >+=head2 unload >+ >+ $validator->unload; >+ >+ Unload custom rules, reload standard rules. >+ >+=cut >+ >+sub unload { >+ my ($self) = @_; >+ $self->{_rules} = {}; >+ return $self->load($standard_rules); >+} >+ >+=head2 check >+ >+ $validator->check( [ 1, 2, 3 ] ); >+ $validator->check( { a => 1, b => 2, c => 3 } ); >+ >+ Apply loaded rules to a set of values (in arrayref or hashref). >+ Returns either validated results (ref) or undef. But if throw_exception >+ is set, a validation error will trigger an exception. >+ If replace_inline is set, the passed reference will be modified too. >+ >+=cut >+ >+sub check { >+ my ( $self, $values ) = @_; >+ $self->{_errors} = {}; >+ if ( ref($values) eq 'HASH' ) { >+ return $self->_check_hash($values); >+ } elsif ( ref($values) eq 'ARRAY' ) { >+ return $self->_check_list($values); >+ } >+ Koha::Exceptions::BadParameter->throw( parameter => 'values' ); >+} >+ >+=head2 option >+ >+ $value = $validator->option('some_option'); # get >+ $validator->option( reset => 1 ); # reset to default >+ $validator->option($hash); # set >+ $validator->option(%hash); # set >+ >+ Get or set validator options. >+ >+=cut >+ >+sub option { >+ my ( $self, @args ) = @_; >+ if ( @args == 1 ) { >+ if ( !ref( $args[0] ) ) { >+ return ( any { $_ eq $args[0] } OPTIONS ) ? $self->{ '_' . $args[0] } : undef; >+ } elsif ( ref( $args[0] ) eq 'HASH' ) { >+ return $self->_set_opts( $args[0] ); >+ } >+ } elsif ( @args == 2 && $args[0] eq 'reset' ) { # reset to default values >+ $self->{_allow_missing_rules} = 0; >+ $self->{_break_on_error} = 1; >+ $self->{_restrict_postfilter} = 1; >+ $self->{_replace_inline} = 0; >+ $self->{_throw_exception} = 1; >+ return $self; >+ } elsif ( @args % 2 == 0 ) { >+ return $self->_set_opts( {@args} ); >+ } >+ Koha::Exceptions::BadParameter->throw( parameter => [@args] ); >+} >+ >+=head2 errors >+ >+ $count = $validator->errors; >+ $code = $validator->errors('field_name'); >+ $hash = $validator->errors( hash => 1 ); # { field1 => code1, ... } >+ $list = $validator->errors( code => CODE ); # [ field1, ... ] >+ $list = $validator->errors( array | list => 1 ); # [ field1, ... ] >+ >+ Returns error information. >+ >+=cut >+ >+sub errors { >+ my ( $self, @args ) = @_; >+ if ( @args == 0 ) { >+ return scalar keys %{ $self->{_errors} }; >+ } elsif ( @args == 1 ) { >+ return $self->{_errors}->{ $args[0] }; >+ } elsif ( @args == 2 && $args[0] eq 'code' ) { >+ return [ grep { $self->{_errors}->{$_} eq $args[1] } sort keys %{ $self->{_errors} } ]; >+ } elsif ( @args == 2 && $args[0] eq 'hash' ) { >+ return { %{ $self->{_errors} } }; # clone >+ } elsif ( @args == 2 && ( $args[0] eq 'list' || $args[0] eq 'array' ) ) { >+ return [ sort keys %{ $self->{_errors} } ]; >+ } >+ Koha::Exceptions::BadParameter->throw( parameter => [@args] ); >+} >+ >+=head1 INTERNAL ROUTINES >+ >+=cut >+ >+sub _replace_aliases { >+ my $self = shift; >+ foreach my $key ( keys %{ $self->{_rules} } ) { >+ my $rule = $self->{_rules}->{$key}; >+ next if ref($rule) eq 'HASH'; >+ if ( $rule && ref( $self->{_rules}->{$rule} ) eq 'HASH' ) { >+ $self->{_rules}->{$key} = $self->{_rules}->{$rule}; >+ } else { >+ Koha::Exceptions::BadParameter->throw("Bad rule for $key"); >+ } >+ } >+ return $self; >+} >+ >+sub _set_opts { >+ my ( $self, $options ) = @_; >+ foreach my $key ( keys %$options ) { >+ next unless any { $_ eq $key } OPTIONS; # just ignores unknown options >+ $self->{"_$key"} = $options->{$key} ? 1 : 0; >+ } >+ return $self; >+} >+ >+sub _check_hash { >+ my ( $self, $values ) = @_; >+ >+ return unless $self->_check_mandatory($values) || !$self->{_break_on_error}; >+ >+ my $results = {}; >+ foreach my $key ( sort keys %$values ) { >+ my $rule = exists $self->{_rules}->{$key} ? $self->{_rules}->{$key} : $self->{_rules}->{defaultrule}; >+ my $value = $values->{$key}; >+ $results->{$key} = $self->_check_value( $rule, $results, $value, $key ); >+ last if exists $self->{_errors}->{$key} && $self->{_break_on_error}; >+ } >+ return $self->_handle_record_validation_error if scalar %{ $self->{_errors} }; >+ $self->_replace_hash( $results, $values ) if $self->{_replace_inline}; >+ return $results; >+} >+ >+sub _check_mandatory { >+ my ( $self, $values ) = @_; >+ foreach my $key ( keys %{ $self->{_rules} } ) { >+ next unless ref( $self->{_rules}->{$key} ) eq 'HASH' && $self->{_rules}->{$key}->{mandatory}; >+ if ( !exists $values->{$key} ) { >+ $self->_handle_validation_error( $key, undef, 'MANDATORY' ); >+ return if $self->{_break_on_error}; >+ } >+ } >+ return !scalar %{ $self->{_errors} }; >+} >+ >+sub _handle_record_validation_error { >+ my $self = shift; >+ >+ # coming here implies that break_on_error is false >+ if ( $self->{_throw_exception} ) { >+ Koha::Exceptions::BadParameter->throw( parameter => $self->errors( hash => 1 ) ); >+ } >+ return; >+} >+ >+sub _replace_hash { >+ my ( $self, $results, $values ) = @_; >+ map { $values->{$_} = $results->{$_} } keys %$results; >+} >+ >+sub _check_list { >+ my ( $self, $values ) = @_; >+ >+ my $index = 0; >+ my $results = []; >+ foreach my $value (@$values) { >+ my $rule = $self->{_rules}->{defaultrule}; >+ $results->[$index] = $self->_check_value( $rule, $results, $value, $index ); >+ last if exists $self->{_errors}->{$index} && $self->{_break_on_error}; >+ $index++; >+ } >+ return $self->_handle_record_validation_error if scalar %{ $self->{_errors} }; >+ $self->_replace_list( $results, $values ) if $self->{_replace_inline}; >+ return $results; >+} >+ >+sub _replace_list { >+ my ( $self, $results, $values ) = @_; >+ splice @$values, 0, scalar @$values, @$results; >+} >+ >+sub _check_value { >+ my ( $self, $rule, $results, $value, $index ) = @_; >+ >+ my ( $res, $code ); >+ >+ # Is rule defined, should it exist? >+ if ( defined($rule) ) { >+ $res = ref($rule) eq 'HASH' ? 1 : 0; >+ } else { >+ $res = $self->{_allow_missing_rules}; >+ $rule = {}; # prevent warns >+ } >+ $code = 'RULE' unless $res; >+ >+ # Check type of value? >+ if ( $res && exists $rule->{ref} ) { >+ my $type = $rule->{ref} // q{}; >+ $res = ref($type) eq q{} && ref($value) eq $type; >+ $code = 'REF' unless $res; >+ } >+ >+ # Insert defaultvalue? >+ if ( $res && exists $rule->{defaultvalue} ) { >+ $value = $rule->{defaultvalue} if !defined($value); >+ } >+ >+ if ( $res && exists $rule->{prefilter} ) { >+ $res = ref( $rule->{prefilter} ) eq 'CODE'; >+ try { $value = $rule->{prefilter}->( $value, $index ) } >+ catch { $res = 0 } >+ if $res; >+ $code = 'PREFILTER' unless $res; >+ } >+ >+ # Validation: values, regex and code >+ if ( $res && exists $rule->{values} ) { >+ $res = ref( $rule->{values} ) eq 'ARRAY' && any { $_ eq $value } @{ $rule->{values} }; >+ $code = 'VALUES' unless $res; >+ } >+ if ( $res && exists $rule->{regexp} ) { >+ $res = ref( $rule->{regexp} ) eq 'Regexp' && $value =~ $rule->{regexp}; >+ $code = 'REGEXP' unless $res; >+ } >+ if ( $res && exists $rule->{code} ) { >+ $res = ref( $rule->{code} ) eq 'CODE'; >+ try { $res = $rule->{code}->( $value, $index ) } >+ catch { $res = 0 } >+ if $res; >+ $code = 'CODE' unless $res; >+ } >+ >+ if ( $res && exists $rule->{postfilter} ) { >+ if ( $res = ref( $rule->{postfilter} ) eq 'CODE' ) { >+ my $data; >+ $data = ref($results) eq 'HASH' ? {%$results} : [@$results] if $self->{_restrict_postfilter}; #clone >+ try { $value = $rule->{postfilter}->( $value, $index, $data // $results ) } >+ catch { $res = 0 }; >+ } >+ $code = 'POSTFILTER' unless $res; >+ } >+ >+ $self->_handle_validation_error( $index, $value, $code ) unless $res; >+ return $value; >+} >+ >+sub _handle_validation_error { >+ my ( $self, $index, $value, $code ) = @_; >+ $self->{_errors}->{$index} = $code; >+ if ( $self->{_throw_exception} && $self->{_break_on_error} ) { >+ Koha::Exceptions::BadParameter->throw( parameter => [ $index, $value, $code ] ); >+ } >+} >+ >+1; >diff --git a/t/Koha/Validator.t b/t/Koha/Validator.t >new file mode 100755 >index 0000000000..6bcc198ca0 >--- /dev/null >+++ b/t/Koha/Validator.t >@@ -0,0 +1,379 @@ >+#!/usr/bin/perl >+ >+# Copyright 2023 Rijksmuseum >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha 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. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+use Data::Dumper qw( Dumper ); >+use Test::More tests => 5; >+use Test::Exception; >+use Test::Warn; >+use Try::Tiny; >+ >+use Koha::Validator; >+use Koha::Email; >+ >+our $validator; >+ >+subtest 'new' => sub { >+ plan tests => 2; >+ >+ $validator = Koha::Validator->new; >+ is( ref($validator), 'Koha::Validator', 'Object type' ); >+ is( $validator->option('throw_exception'), 1, 'Check if option has default value' ); >+}; >+ >+subtest 'option' => sub { >+ plan tests => 10; >+ >+ is( ref( $validator->option ), 'Koha::Validator', 'Called option without parameters' ); >+ is( $validator->option('a'), undef, 'Check unknown option' ); >+ is( $validator->option('break_on_error'), 1, 'Check option break_on_error' ); >+ is( $validator->option('replace_inline'), 0, 'Check option replace_inline' ); >+ >+ is( ref $validator->option( a => 1 ), 'Koha::Validator', 'Trying to set unknown option' ); >+ is( $validator->option('a'), undef, 'Check unknown option again' ); >+ >+ $validator->option( a => 1, b => 2, replace_inline => 3, REPLACE_INLINE => 0 ); # last uc option incorrect >+ is( $validator->option('replace_inline'), 1, 'Check option replace_inline after setting with hash' ); >+ $validator->option( { a => 1, b => 2, break_on_error => 0, replace_inline => undef } ); >+ is( $validator->option('replace_inline'), 0, 'Check option replace_inline after setting with hashref' ); >+ is( $validator->option( reset => 1 )->option('break_on_error'), 1, 'break_on_error has been reset' ); >+ throws_ok { $validator->option( a => 1, b => 2, 3 ) } 'Koha::Exceptions::BadParameter', 'Bad number of parameters'; >+}; >+ >+subtest 'load' => sub { >+ plan tests => 15; >+ >+ throws_ok { $validator->load } 'Koha::Exceptions::BadParameter', >+ 'No input for load'; >+ throws_ok { $validator->load('wrong') } 'Koha::Exceptions::BadParameter', >+ 'Wrong input for load'; >+ throws_ok { $validator->load( { defaultrule => undef } ) } 'Koha::Exceptions::BadParameter', >+ 'Rule should not be undefined'; >+ throws_ok { $validator->load( { defaultrule => 'test' } ) } 'Koha::Exceptions::BadParameter', >+ 'The alias \'test\' cannot be resolved'; >+ >+ $validator->option( throw_exception => 0, allow_missing_rules => 1 ); >+ >+ is( ref $validator->load( { defaultrule => {} } ), 'Koha::Validator', 'Load returned object' ); >+ ok( $validator->check( { 1 => 2 } ), 'Checked hash with empty defaultrule' ); >+ ok( $validator->check( [ 1, 2 ] ), 'Checked array with empty defaultrule' ); >+ $validator->load( { defaultrule => { code => sub { 1 } } } ); >+ ok( $validator->check( { 1 => 2 } ), 'Checked hash with true sub' ); >+ ok( $validator->check( [ 1, 2 ] ), 'Checked array with true sub' ); >+ $validator->load( { defaultrule => { code => sub { } } } ); >+ ok( !$validator->check( { 1 => 2 } ), 'Checked hash with false sub' ); >+ ok( !$validator->check( [ 1, 2 ] ), 'Checked array with false sub' ); >+ >+ $validator->unload->load( { my_column => { values => [ 2, 3 ] } } ); >+ ok( $validator->check( [1] ), 'Checked array without defaultrule' ); >+ ok( $validator->check( { my_column => 3 } ), 'Checked hash with single rule' ); >+ >+ # Add standard rules, overwrite >+ $Koha::Validator::standard_rules = { rule1 => { regexp => qr/\D/ }, rule2 => { defaultvalue => 1 } }; >+ ok( $validator->unload->check( { rule1 => '2a' } ), 'Standard rule applied' ); >+ $validator->load( { rule1 => { postfilter => sub { 2 } } } ); >+ is_deeply( >+ $validator->check( { rule1 => '2a', rule2 => undef } ), { rule1 => 2, rule2 => 1 }, >+ 'Rule1 overwritten' >+ ); >+ $Koha::Validator::standard_rules = {}; >+ >+ $validator->unload->option( reset => 1 ); >+}; >+ >+subtest 'check' => sub { >+ plan tests => 19; >+ >+ throws_ok { $validator->check } 'Koha::Exceptions::BadParameter', 'No values to check'; >+ throws_ok { $validator->check(1) } 'Koha::Exceptions::BadParameter', 'Check does only accept hashref or arrayref'; >+ ok( $validator->check( {} ), 'Empty hash validated' ); >+ ok( $validator->check( [] ), 'Empty list validated' ); >+ >+ subtest 'arrayref' => sub { >+ $validator->load( { defaultrule => { values => [ 1, 2, 3 ] } } )->option( throw_exception => 0 ); >+ ok( $validator->check( [ 1, 2, '2', 3, '1' ] ), 'Check array of integers and characters with defaultrule' ); >+ >+ my $code = sub { >+ my ( $value, $index ) = @_; >+ return ( $value == 2 or $value == 3 ) if $index == 0; >+ return $value == 2 if $index == 1; >+ return $value =~ qr/test/ if $index == 2; >+ return 0 if $index == 3; >+ return 0; >+ }; >+ $validator->load( { defaultrule => { ref => '', code => $code } } ); >+ >+ ok( !$validator->check( [ 1, ] ), 'Checked array with one element' ); >+ is( $validator->errors(0), 'CODE', 'Check error' ); >+ ok( $validator->check( [ 3, ] ), 'Checked another array with one element' ); >+ ok( !$validator->check( [ [1] ] ), 'Checked another array with one nested arrayref' ); >+ ok( !$validator->check( [ 2, 3 ] ), 'Checked array with two elements, fallback fails on index 1' ); >+ is( $validator->errors(1), 'CODE', 'Check error for two elements array' ); >+ ok( $validator->check( [ 2, '2', 'testcase' ] ), 'Checked array with three elements' ); >+ ok( !$validator->check( [ 2, '2', 'textcase' ] ), 'Checked another array with three elements' ); >+ is( $validator->errors(2), 'CODE', 'Check error for three elements array' ); >+ ok( !$validator->check( [ 2, '2', 'testcase', 'index3' ] ), 'Checked array with four elements' ); >+ is( $validator->errors(3), 'CODE', 'Check error for four elements array' ); >+ >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'hashref' => sub { >+ my $rules = { >+ digits_only => { regexp => qr/^\d+$/ }, >+ email => { code => sub { Koha::Email->is_valid( $_[0] ) } }, >+ test01 => 'digits_only', >+ test02 => { values => [ 1, 2 ] }, >+ test03 => { code => sub { 0 } }, >+ test04 => 'email', >+ test05 => { regexp => qr/test/ }, >+ }; >+ $validator->load($rules); >+ my $data = { test01 => 2, test02 => 1, test04 => 'marcel@test.nl', test05 => 'includes test word' }; >+ try { $validator->check($data); ok( 1, 'validator green' ) } >+ catch { ok(0) }; >+ is( $validator->errors, 0, 'No errors encountered' ); >+ >+ $data = { test01 => 2, test02 => 1, test03 => 'nomatterwhat' }; >+ try { $validator->check($data); ok(0) } >+ catch { ok( 1, 'validator red' ) }; >+ is( $validator->errors('test03'), 'CODE', 'Check error code for test03' ); >+ >+ my $code = sub { return Koha::Email->is_valid( $_[0] ) && $_[0] !~ /[\`]/ }; >+ $validator->load( { defaultrule => 'email', email => { code => $code } } ); >+ $data = { email => 'withbacktick`ls`@test.com' }; >+ try { $validator->check($data); ok(0) } >+ catch { is( $validator->errors('email'), 'CODE', 'validator red for email with backticks' ) }; >+ ok( $validator->check( { emailpro => 'john@doe.com' } ), 'John Doe never fails' ); >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'mandatory' => sub { >+ $validator->option( throw_exception => 0 ); >+ $validator->load( { test => { mandatory => 1 }, test2 => {}, test3 => { mandatory => 0 } } ); >+ ok( !$validator->check( {} ), 'Missing test column' ); >+ is( $validator->errors('test'), 'MANDATORY', 'Check field' ); >+ ok( $validator->check( { test => 1, test2 => 0 } ), 'only test should be present' ); >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'ref' => sub { >+ $validator->option( break_on_error => 0, throw_exception => 0 ); >+ $validator->load( { a => { ref => q{} }, b => { ref => 'HASH' }, c => {}, d => { ref => [1] } } ); >+ ok( !$validator->check( { a => [], b => q{}, c => 1 } ), 'a and b should fail' ); >+ is_deeply( $validator->errors( code => 'REF' ), [ 'a', 'b' ], 'Check wrong fields' ); >+ ok( !$validator->check( { d => 1 } ), 'Wrong ref definition, scalar expected' ); >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'defaultvalue' => sub { >+ $validator->load( { defaultrule => { defaultvalue => 1 } } ); >+ is_deeply( $validator->check( [ undef, q{}, 0, 2 ] ), [ 1, q{}, 0, 2 ], 'defaultvalue on array' ); >+ is_deeply( $validator->check( { a => undef, b => q{} } ), { a => 1, b => q{} }, 'defaultvalue on hash' ); >+ $validator->unload; >+ }; >+ >+ subtest 'prefilter' => sub { >+ $validator->load( { test => { prefilter => sub { $_[0] =~ s/\s//g; $_[0] } } } ); >+ is_deeply( $validator->check( { test => 'a b c ' } ), { test => 'abc' }, 'Prefilter removed spaces' ); >+ $validator->load( { test => { prefilter => undef } } ); >+ throws_ok { $validator->check( { test => 'a b c ' } ) } 'Koha::Exceptions::BadParameter', >+ 'Wrong prefilter, no coderef'; >+ $validator->unload; >+ }; >+ >+ subtest 'values' => sub { >+ $validator->option( break_on_error => 0, throw_exception => 0 ); >+ >+ # Note: values for a and b are bad definitions! >+ $validator->load( { a => { values => undef }, b => { values => 1 }, c => { values => [ 10, 20 ] } } ); >+ $validator->check( { a => 1, b => 1, c => 1 } ); >+ is_deeply( $validator->errors( code => 'VALUES' ), [ 'a', 'b', 'c' ], 'a, b and c failed' ); >+ $validator->unload; >+ $validator->load( { defaultrule => { values => [ 1, 2 ] }, c => { values => [3] } } ); >+ ok( $validator->check( { a => 1, b => 2, c => 3 } ), 'All passed now' ); >+ >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'regexp' => sub { >+ $validator->option( break_on_error => 0 ); >+ $validator->load( { defaultrule => { regexp => '/test/i' } } ); # bad defined >+ try { $validator->check( [1] ); ok(0) } >+ catch { ok( 1, 'Bad regex definition' ) }; >+ $validator->load( { defaultrule => { regexp => qr/\d\D/ } } ); >+ try { $validator->check( [ '123', '1b', 'ab2' ] ); ok(0) } >+ catch { ok( 1, 'Should fail on some elements' ) }; >+ is_deeply( $validator->errors( code => 'REGEXP' ), [ 0, 2 ], 'Two elements failed' ); >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'code' => sub { >+ $validator->load( { test1 => { code => undef }, test2 => { code => 1 } } ); # bad defined >+ try { $validator->check( { test1 => 1 } ); ok(0) } >+ catch { ok( 1, 'Should fail on code undef' ) }; >+ try { $validator->check( { test2 => 2 } ); ok(0) } >+ catch { ok( 1, 'Should fail on code == 1' ) }; >+ $validator->load( >+ { defaultrule => { code => sub { $_[0] =~ /a/ } }, test2 => { code => sub { $_[0] =~ /b/ } } } ); >+ my $res = $validator->check( { test => 'Aa', test2 => 'bB' } ); >+ $validator->unload; >+ }; >+ >+ subtest 'postfilter' => sub { >+ $validator->option( throw_exception => 0 ); >+ $validator->load( >+ { >+ defaultrule => { >+ prefilter => sub { $_[0] =~ s/\s//g; $_[0] }, >+ postfilter => sub { uc $_[0] }, >+ }, >+ test => {}, >+ test3 => { >+ postfilter => sub { die }, >+ }, >+ } >+ ); >+ my $results = $validator->check( { test => 'a b c ', test2 => 'd e' } ); >+ is( $results->{test}, 'a b c ', 'Field \'test\' does not hit postfilter' ); >+ is( $results->{test2}, 'DE', 'Test2 passed postfilter of defaultrule' ); >+ >+ # what if postfilter crashes >+ ok( !$validator->check( { test => 'a b c ', test2 => 'd e', test3 => 'f g' } ), 'Should stumble over die' ); >+ is( $validator->errors('test3'), 'POSTFILTER', 'Check which field did not validate' ); >+ >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'allow_missing_rules' => sub { >+ $validator->option( allow_missing_rules => 1, throw_exception => 0 ); >+ $validator->load( { test => { regexp => qr/\d/ } } ); >+ ok( $validator->check( { test => 2 } ), 'Validated' ); >+ ok( $validator->check( { tezt => 3 } ), 'No rule for tezt is allowed here' ); >+ $validator->option( allow_missing_rules => 0 ); >+ ok( !$validator->check( { tezt => 3 } ), 'No rule for tezt no longer allowed' ); >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'break_on_error' => sub { >+ $validator->option( throw_exception => 0 ); >+ $validator->load( { a => { values => [ 1, 2 ] }, b => { values => [ 2, 3 ] } } ); >+ ok( !$validator->check( { a => 0, b => 0 } ), 'Wrong values for a and b' ); >+ is_deeply( $validator->errors( array => 1 ), ['a'], 'Stopped when a was wrong' ); >+ $validator->option( break_on_error => 0 ); >+ ok( !$validator->check( { a => 0, b => 0 } ), 'Check a and b again' ); >+ is_deeply( $validator->errors( array => 1 ), [ 'a', 'b' ], 'Found a and b now' ); >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'replace_inline' => sub { >+ $validator->option( replace_inline => 0, throw_exception => 0 ); >+ $validator->load( { defaultrule => { ref => q{}, postfilter => sub { $_[0] + 1 } } } ); >+ >+ my $array = [ 1, 2 ]; >+ my $hash = { a => 1, b => 2 }; >+ is_deeply( $validator->check($array), [ 2, 3 ], 'Check results array' ); >+ is_deeply( $validator->check($hash), { a => 2, b => 3 }, 'Check results hash' ); >+ is_deeply( $array, [ 1, 2 ], 'Original array untouched' ); >+ is_deeply( $hash, { a => 1, b => 2 }, 'Original hash untouched' ); >+ >+ $validator->option( replace_inline => 1 ); >+ >+ # First, add an invalid argument (reference); check and test if not modified >+ $array = [ 1, 2, {} ]; >+ $hash = { a => 1, b => 2, c => [] }; >+ ok( !$validator->check($array), 'Array does no longer validate' ); >+ ok( !$validator->check($hash), 'Hash does no longer validate' ); >+ is_deeply( $array, [ 1, 2, {} ], 'Original array untouched' ); >+ is_deeply( $hash, { a => 1, b => 2, c => [] }, 'Original hash untouched' ); >+ >+ # Pass valid content, and test if modified >+ $array = [ 1, 2 ]; >+ $hash = { a => 1, b => 2 }; >+ $validator->check($array); >+ $validator->check($hash); >+ is_deeply( $array, [ 2, 3 ], 'Original array modified' ); >+ is_deeply( $hash, { a => 2, b => 3 }, 'Original hash modified' ); >+ >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'restrict_postfilter' => sub { >+ # Example of 3 fields, a == b + c; note that validations starts with a.. >+ # Postfilter can only validate by throwing an exception. >+ my $code = sub { my ( $val, $idx, $res ) = @_; die unless $res->{a} == $res->{b} + $val; $val }; >+ $validator->load( { a => {}, b => {}, c => { postfilter => $code } } ); >+ ok( $validator->check( { a => 3, b => 2, c => 1 } ), 'Passes' ); >+ try { $validator->check( { a => 4, b => 2, c => 1 } ); ok(0) } >+ catch { ok( 1, 'Should fail' ) }; >+ >+ # Now try to make postfilter correct/enforce the value of a (restrict_postfilter on) >+ $code = sub { my ( $val, $idx, $res ) = @_; $res->{a} = $res->{b} + $val; $val }; >+ $validator->load( { a => {}, b => {}, c => { postfilter => $code } } ); >+ my $res = $validator->check( { a => 4, b => 2, c => 1 } ); >+ is( $res->{a}, 4, 'Postfilter failed to adjust the value of a' ); >+ # Correct attempt: restrict_postfilter off >+ $validator->option( restrict_postfilter => 0 ); >+ $res = $validator->check( { a => 4, b => 2, c => 1 } ); >+ is( $res->{a}, 3, 'Postfilter did adjust the value of a, restrict_postfilter off' ); >+ >+ $validator->unload->option( reset => 1 ); >+ }; >+ >+ subtest 'throw_exception' => sub { >+ try { $validator->check( [1] ); ok(0) } >+ catch { ok( 1, 'Exception for array without defaultrule' ) }; >+ $validator->option( throw_exception => 0 ); >+ my $res; >+ lives_ok { $res = $validator->check( [1] ) } 'No exception for same check'; >+ is( $res, undef, 'Returned undef' ); >+ $validator->unload->option( reset => 1 ); >+ }; >+}; >+ >+subtest 'errors' => sub { >+ plan tests => 14; >+ $validator->option( throw_exception => 0 ); >+ $validator->load( >+ { >+ defaultrule => { ref => q{}, values => [ 1, 2 ], regexp => qr/[3-9]/ }, >+ test => { mandatory => 1, code => sub { } } >+ } >+ ); >+ ok( !$validator->check( [ 0, {}, 2 ] ), 'Three elements should fail' ); >+ is( $validator->errors, 1, 'Check error count, break on' ); >+ is( $validator->errors(0), 'VALUES', 'Check error of index 0, break on' ); >+ is_deeply( $validator->errors( hash => 1 ), { 0 => 'VALUES' }, 'Check hash of errors, break on' ); >+ is_deeply( $validator->errors( list => 1 ), [0], 'Check list of errors, break on' ); >+ is_deeply( $validator->errors( code => 'REF' ), [], 'Check list of errors for REF, break on' ); >+ is_deeply( $validator->errors( code => 'VALUES' ), [0], 'Check list of errors for VALUES, break on' ); >+ >+ $validator->option( break_on_error => 0 ); >+ ok( !$validator->check( [ 0, {}, 2 ] ), 'Three elements should fail again' ); >+ is( $validator->errors, 3, 'Check error count, break off' ); >+ is( $validator->errors(2), 'REGEXP', 'Check error of index 2, break off' ); >+ is_deeply( >+ $validator->errors( hash => 1 ), { 0 => 'VALUES', 1 => 'REF', 2 => 'REGEXP' }, >+ 'Check hash of errors, break off' >+ ); >+ is_deeply( $validator->errors( array => 1 ), [ 0, 1, 2 ], 'Check array of errors, break off' ); >+ is_deeply( $validator->errors( code => 'REF' ), [1], 'Check list of errors for REF, break off' ); >+ is_deeply( $validator->errors( code => 'CODE' ), [], 'Check list of errors for CODE, break off' ); >+ $validator->unload->option( reset => 1 ); >+}; >-- >2.30.2
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 34430
:
154125
| 154768