package HTML::FormFu::Constraint;
use strict;
use base 'HTML::FormFu::Processor';
use Class::C3;
use HTML::FormFu::Exception::Constraint;
use HTML::FormFu::Util qw(
DEBUG_CONSTRAINTS
debug
);
use List::MoreUtils qw( any );
use Scalar::Util qw( blessed );
use Storable qw( dclone );
use Carp qw( croak );
__PACKAGE__->mk_item_accessors(qw( not force_errors when ));
sub process {
my ( $self, $params ) = @_;
my $value = $self->get_nested_hash_value( $params, $self->nested_name );
my @errors;
# check when condition
if ( !$self->_process_when($params) ) {
DEBUG_CONSTRAINTS && debug('fail when() check - skipping constraint');
return;
}
if ( ref $value eq 'ARRAY' ) {
push @errors, eval { $self->constrain_values( $value, $params ) };
if ($@) {
push @errors,
$self->mk_errors( {
pass => 0,
message => $@,
} );
}
}
else {
my $ok = eval { $self->constrain_value( $value, $params ) };
DEBUG_CONSTRAINTS && debug( 'CONSTRAINT RETURN VALUE' => $ok );
DEBUG_CONSTRAINTS && debug( '$@' => $@ );
push @errors,
$self->mk_errors( {
pass => ( $@ || !$ok ) ? 0 : 1,
message => $@,
} );
}
return @errors;
}
sub constrain_values {
my ( $self, $values, $params ) = @_;
my @errors;
for my $value (@$values) {
my $ok = eval { $self->constrain_value( $value, $params ) };
DEBUG_CONSTRAINTS && debug( 'CONSTRAINT RETURN VALUE' => $ok );
DEBUG_CONSTRAINTS && debug( '$@' => $@ );
push @errors,
$self->mk_errors( {
pass => ( $@ || !$ok ) ? 0 : 1,
message => $@,
} );
}
return @errors;
}
sub constrain_value {
croak "constrain_value() should be overridden";
}
sub mk_errors {
my ( $self, $args ) = @_;
my $pass = $args->{pass};
my $message = $args->{message};
my @errors;
my $force = $self->force_errors || $self->parent->force_errors;
if ( !$pass || $force ) {
my $error = $self->mk_error($message);
$error->forced(1) if $pass;
push @errors, $error;
}
return @errors;
}
sub mk_error {
my ( $self, $err ) = @_;
if ( !blessed $err || !$err->isa('HTML::FormFu::Exception::Constraint') ) {
$err = HTML::FormFu::Exception::Constraint->new;
}
return $err;
}
sub _process_when {
my ( $self, $params ) = @_;
# returns 1 if when condition is fullfilled or not defined
# returns 0 if when condition is defined and not fullfilled
# If it's a callback, return callback's return value (so when
# condition is met if callback returns a true value)
# get when condition
my $when = $self->when;
return 1 if !defined $when;
# check type of 'when'
croak "Parameter 'when' is not a hash ref" if ref $when ne 'HASH';
# field or callback must be defined
my $when_field = $when->{field};
my $when_callback = $when->{callback};
croak "Parameter 'field' or 'callback' is not defined"
if !defined $when_field && !defined $when_callback;
# Callback will be the preferred thing
if ($when_callback) {
no strict 'refs';
return $when_callback->($params);
}
# nothing to constrain if field doesn't exist
my $when_field_value = $self->get_nested_hash_value( $params, $when_field );
return 0 if !defined $when_field_value;
my @values;
if ( defined( my $value = $when->{value} ) ) {
push @values, $value;
}
elsif ( defined( my $values = $when->{values} ) ) {
push @values, @$values;
}
# determine if condition is fulfilled
my $ok;
if (@values) {
$ok = any { $when_field_value eq $_ } @values;
}
else {
$ok = $when_field_value ? 1 : 0;
}
# invert when condition if asked for
$ok = $when->{not} ? !$ok : $ok;
return $ok;
}
sub clone {
my $self = shift;
my $clone = $self->next::method(@_);
if ( defined( my $when = $self->when ) ) {
$clone->when( dclone $when );
}
return $clone;
}
1;
__END__
=head1 NAME
HTML::FormFu::Constraint - Constrain User Input
=head1 SYNOPSIS
---
elements:
- type: Text
name: foo
constraints:
- type: Length
min: 8
when:
field: bar
values: [ 1, 3, 5 ]
- type: Text
name: bar
constraints:
- Integer
- Required
constraints:
- SingleValue
=head1 DESCRIPTION
User input is processed in the following order:
=over
=item L
=item L
=item L
=item L
=item L
=back
See L for further details.
L can be called on any L