package Input::Validator;
use strict;
use warnings;
use base 'Input::Validator::Base';
our $VERSION = '0.001005';
use Input::Validator::Bulk;
use Input::Validator::Condition;
use Input::Validator::Field;
use Input::Validator::Group;
require Carp;
sub BUILD {
my $self = shift;
$self->{fields} = {};
$self->{conditions} = [];
$self->{groups} = [];
$self->{messages} ||= {};
$self->{trim} = 1 unless defined $self->{trim};
$self->{explicit} ||= 0;
return $self;
}
sub messages { @_ > 1 ? $_[0]->{messages} = $_[1] : $_[0]->{messages} }
sub trim { @_ > 1 ? $_[0]->{trim} = $_[1] : $_[0]->{trim} }
sub explicit { @_ > 1 ? $_[0]->{explicit} = $_[1] : $_[0]->{explicit} }
sub field {
my $self = shift;
# Return field if it is already created
return $self->{fields}->{$_[0]}
if @_ == 1 && ref($_[0]) ne 'ARRAY' && $self->{fields}->{$_[0]};
# Accept array or arrayref
my @names = @_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_;
my $fields = [];
foreach my $name (@names) {
my $field = Input::Validator::Field->new(
name => $name,
messages => $self->{messages},
explicit => $self->{explicit},
);
$self->{fields}->{$name} = $field;
push @$fields, $field;
}
return $self->{fields}->{$names[0]} if @names == 1;
return Input::Validator::Bulk->new(fields => $fields);
}
sub when {
my $self = shift;
my $cond = Input::Validator::Condition->new->when(@_);
push @{$self->{conditions}}, $cond;
return $cond;
}
sub group {
my $self = shift;
my $name = shift;
my $fields = shift;
if (my ($exists) = grep { $_->name eq $name } @{$self->{groups}}) {
Carp::croak("Fields of group '$name' already defined.") if $fields;
return $exists;
}
$fields = [map { $self->{fields}->{$_} } @$fields];
my $group = Input::Validator::Group->new(name => $name, fields => $fields);
push @{$self->{groups}}, $group;
return $group;
}
sub has_unknown_params {
my $self = shift;
return $self->{has_unknown_params};
}
sub has_errors {
my $self = shift;
my $errors = $self->{errors};
return 1 if $errors && scalar keys %$errors;
return 0;
}
sub error {
my $self = shift;
my ($name, $message) = @_;
$self->{errors} ||= {};
$self->{errors}->{$name} = $message;
return $self;
}
sub errors {
my $self = shift;
return $self->{errors};
}
sub clear_errors {
my $self = shift;
# Clear field errors
foreach my $field (CORE::values %{$self->{fields}}) {
$field->error('');
}
# Clear group errors
foreach my $group (@{$self->{groups}}) {
$group->error('');
}
$self->{errors} = {};
}
sub validate {
my $self = shift;
my $params = shift;
while (1) {
$self->clear_errors;
$self->_flag_unknown($params);
$self->_populate_fields($params);
$self->_validate_fields;
$self->_validate_groups;
my @conditions =
grep { !$_->is_matched && $_->match($self->{fields}) }
@{$self->{conditions}};
last unless @conditions;
foreach my $cond (@conditions) {
$cond->then->($self);
}
}
return $self->has_errors ? 0 : 1;
}
sub _flag_unknown {
my $self = shift;
my $params = shift;
foreach my $param (keys %$params) {
if (!defined $self->{fields}->{$param}) {
$self->{has_unknown_params} = 1;
if ($self->{explicit}) {
$self->error($param => $self->{messages}->{'NOT_SPECIFIED'}
|| 'NOT_SPECIFIED');
}
}
}
}
sub _populate_fields {
my $self = shift;
my $params = shift;
foreach my $field (CORE::values %{$self->{fields}}) {
$field->clear_value;
$field->value($params->{$field->name});
}
}
sub _validate_fields {
my $self = shift;
my $params = shift;
foreach my $field (CORE::values %{$self->{fields}}) {
next if $field->is_valid;
$self->error($field->name => $field->error) if $field->error;
}
}
sub _validate_groups {
my $self = shift;
foreach my $group (@{$self->{groups}}) {
next if $group->is_valid;
$self->error($group->name => $group->error) if $group->error;
}
}
sub values {
my $self = shift;
my $values = {};
foreach my $field (CORE::values %{$self->{fields}}) {
$values->{$field->name} = $field->value
if defined $field->value && !$field->error;
}
return $values;
}
sub all_values {
my $self = shift;
my $values = {};
foreach my $field (CORE::values %{$self->{fields}}) {
$values->{$field->name} = $field->value
if defined $field->value;
}
return $values;
}
1;
__END__
=head1 NAME
Input::Validator - Input Validator
=head1 SYNOPSIS
my $validator = Input::Validator->new;
# Fields
$validator->field('phone')->required(1)->regexp(qr/^\d+$/);
$validator->field([qw/firstname lastname/])
->each(sub { shift->required(1)->length(3, 20) });
# Groups
$validator->field([qw/password confirm_password/])
->each(sub { shift->required(1) });
$validator->group('passwords' => [qw/password confirm_password/])->equal;
# Conditions
$validator->field('document');
$validator->field('number');
$validator->when('document')->regexp(qr/^1$/)
->then(sub { shift->field('number')->required(1) });
$validator->validate($values_hashref);
my $errors_hashref = $validator->errors;
my $pass_error = $validator->group('passwords')->error;
my $validated_values_hashref = $validator->values;
=head1 DESCRIPTION
Data validator. Validates only the data. B<NO> form generation, B<NO> javascript
generation, B<NO> other stuff that does something else. Only data validation!
=head1 FEATURES
=over 4
Validates data that is presented as a hash reference
Multiple values
Field registration
Group validation
Conditional validation
=back
=head1 CONVENTIONS
=over 4
A value is considered empty when its value is C<undef>, C<''> or
contains only spaces.
If a value is not required and during validation is empty there is B<NO>
error.
If explicit is set to true, then all values not explicitly required
generate an error.
If a value is passed as an array reference and an appropriate field is
not multiple, than only the first value is taken, otherwise every value of
the array reference is checked.
=back
=head1 ATTRIBUTES
=head2 C<explicit>
Causes errors to be generated when unknown parameters exist.
=head2 C<has_unknown_params>
Unknown parameters exist in validated parameter hashref.
=head2 C<messages>
my $validator =
Input::Validator->new(
messages => {REQUIRED => 'This field is required'});
Replace default messages.
=head2 C<trim>
Trim field values. B<ON> by default.
=head1 METHODS
=head2 C<new>
my $validator = Input::Validator->new;
Create a new L<Input::Validator> object.
=head2 C<clear_errors>
$validator->clear_errors;
Clear errors.
=head2 C<field>
$validator->field('foo'); # Input::Validator::Field object is returned
$validator->field('foo'); # Already created field object is returned
$validator->field(qw/foo bar baz/); # Input::Validator::Bulk object is returned
$validator->field([qw/foo bar baz/]); # Input::Validator::Bulk object is returned
When a single value is passed create L<Input::Validator::Field> object or
return an already created field object.
When an array or an array reference is passed return L<Input::Validator::Bulk> object. You can
call C<each> method to apply setting to multiple fields.
$validator->field(qw/foo bar baz/)->each(sub { shift->required(1) });
=head2 C<group>
$validator->field(qw/foo bar/)->each(sub { shift->required(1) });
$validator->group('all_or_none' => [qw/foo bar/])->equal;
Register a group constraint that will be called on group of fields. If group
validation fails the C<errors> hashref will have the B<group> name with an
appropriate error message, B<NOT> fields' names.
=head2 C<when>
$validator->field('document');
$validator->field('number');
$validator->when('document')->regexp(qr/^1$/)
->then(sub { shift->field('number')->required(1) });
Register a condition that is called when some conditions are met. You can do
whatever you want in condition's callback. Validation will be remade.
=head2 C<validate>
$validator->validate({a => 'b'});
$validator->validate({a => ['b', 'c']});
$validator->validate({a => ['b', 'c'], b => 'd'});
Accept and validate a hash reference that represents data that is being
validated. Input values can be either a C<SCALAR> value or an C<ARRAREF> value,
which means that a field has multiple values. In case of an array reference, it
is checked if a field can have multiple values. Otherwise only the first value
is accepted and returned when C<values> method is called.
=head2 C<error>
$validator->error(foo => 'bar');
Set a custom error.
=head2 C<errors>
$validator->errors; # {a => 'Required'}
Return a hash reference of errors.
=head2 C<has_errors>
$validator->has_errors;
Check if there are any errors.
=head2 C<values>
$validator->values;
Return a hash reference of validated values. Only registered fields are returned,
that means that if some other values were passed to the C<validate> method they
are ignored.
=head2 C<all_values>
$validator->all_values;
Return a hash reference of all values. Only registered fields are returned.
Useful for displaying wrong values for user to reenter.
=head1 DEVELOPMENT
=head2 Repository
http://github.com/vti/input-validator
=head1 AUTHOR
Viacheslav Tykhanovskyi, C<vti@cpan.org>.
=head1 CREDITS
In alphabetical order:
Alex Voronov
Anatoliy Lapitskiy
Glen Hinkle
Marcus Ramberg
Naoya Ito
Yaroslav Korshak
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2011, Viacheslav Tykhanovskyi.
This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.
=cut