package FormValidator::Simple; use strict; use base qw/Class::Accessor::Fast Class::Data::Inheritable Class::Data::Accessor/; use Class::Inspector; use UNIVERSAL::require; use Scalar::Util qw/blessed/; use FormValidator::Simple::Results; use FormValidator::Simple::Exception; use FormValidator::Simple::Data; use FormValidator::Simple::Profile; use FormValidator::Simple::Validator; use FormValidator::Simple::Constants; use FormValidator::Simple::Messages; our $VERSION = '0.29'; __PACKAGE__->mk_classaccessors(qw/data prof results/); __PACKAGE__->mk_classaccessor( messages => FormValidator::Simple::Messages->new ); sub import { my $class = shift; foreach my $plugin (@_) { my $plugin_class; if ($plugin =~ /^\+(.*)/) { $plugin_class = $1; } else { $plugin_class = "FormValidator::Simple::Plugin::$plugin"; } $class->load_plugin($plugin_class); } } sub load_plugin { my ($proto, $plugin) = @_; my $class = ref $proto || $proto; unless (Class::Inspector->installed($plugin)) { FormValidator::Simple::Exception->throw( qq/$plugin isn't installed./ ); } $plugin->require; if ($@) { FormValidator::Simple::Exception->throw( qq/Couldn't require "$plugin", "$@"./ ); } { no strict 'refs'; push @FormValidator::Simple::Validator::ISA, $plugin; } } sub set_option { my $class = shift; while ( my ($key, $val) = splice @_, 0, 2 ) { FormValidator::Simple::Validator->options->{$key} = $val; } } sub set_messages { my ($proto, $file) = @_; my $class = ref $proto || $proto; if (blessed $proto) { $proto->messages(FormValidator::Simple::Messages->new)->load($file); if ($proto->results) { $proto->results->message($proto->messages); } else { $proto->results( FormValidator::Simple::Results->new( messages => $proto->messages, ) ); } } else { $class->messages->load($file); } } sub set_message_decode_from { my ($self, $decode_from) = @_; $self->messages->decode_from($decode_from); } sub set_message_format { my ($proto, $format) = @_; $format ||= ''; $proto->messages->format($format); } sub new { my $proto = shift; my $class = ref $proto || $proto; my $self = bless { }, $class; $self->_init(@_); return $self; } sub _init { my ($self, @args) = @_; my $class = ref $self; $class->set_option(@args); $self->results( FormValidator::Simple::Results->new( messages => $self->messages, ) ); } sub set_invalid { my ($self, $name, $type) = @_; unless (ref $self) { FormValidator::Simple::Exception->throw( qq/set_invalid is instance method./ ); } unless ($name && $type) { FormValidator::Simple::Exception->throw( qq/set_invalid needs two arguments./ ); } $self->results->set_result($name, $type, FALSE); } sub check { my ($proto, $input, $prof, $options) = @_; $options ||= {}; my $self = blessed $proto ? $proto : $proto->new(%$options); my $data = FormValidator::Simple::Data->new($input); my $prof_setting = FormValidator::Simple::Profile->new($prof); my $profile_iterator = $prof_setting->iterator; PROFILE: while ( my $profile = $profile_iterator->next ) { my $name = $profile->name; my $keys = $profile->keys; my $constraints = $profile->constraints; my $params = $data->param($keys); $self->results->register($name); $self->results->record($name)->data( @$params == 1 ? $params->[0] : ''); my $constraint_iterator = $constraints->iterator; if ( scalar @$params == 1 ) { unless ( defined $params->[0] && $params->[0] ne '' ) { if ( $constraints->needs_blank_check ) { $self->results->record($name)->is_blank( TRUE ); } next PROFILE; } } CONSTRAINT: while ( my $constraint = $constraint_iterator->next ) { my ($result, $data) = $constraint->check($params); $self->results->set_result($name, $constraint->name, $result); $self->results->record($name)->data($data) if $data; } } return $self->results; } 1; =head1 NAME FormValidator::Simple - validation with simple chains of constraints =head1 SYNOPSIS my $query = CGI->new; $query->param( param1 => 'ABCD' ); $query->param( param2 => 12345 ); $query->param( mail1 => 'lyo.kato@gmail.com' ); $query->param( mail2 => 'lyo.kato@gmail.com' ); $query->param( year => 2005 ); $query->param( month => 11 ); $query->param( day => 27 ); my $result = FormValidator::Simple->check( $query => [ param1 => ['NOT_BLANK', 'ASCII', ['LENGTH', 2, 5]], param2 => ['NOT_BLANK', 'INT' ], mail1 => ['NOT_BLANK', 'EMAIL_LOOSE'], mail2 => ['NOT_BLANK', 'EMAIL_LOOSE'], { mails => ['mail1', 'mail2' ] } => ['DUPLICATION'], { date => ['year', 'month', 'day'] } => ['DATE'], ] ); if ( $result->has_error ) { my $tt = Template->new({ INCLUDE_PATH => './tmpl' }); $tt->process('template.html', { result => $result }); } template example [% IF result.has_error %]
Found Input Error
mail1 and mail2 aren't same.
[% END %] and here's an another example. my $q = CGI->new; $q->param( year => 2005 ); $q->param( month => 12 ); $q->param( day => 27 ); my $result = FormValidator::Simple->check( $q => [ { date => ['year', 'month', 'day'] } => [ 'DATE' ], ] ); [% IF result.invalid('date') %]Set correct date.
[% END %] =head2 FLEXIBLE VALIDATION my $valid = FormValidator::Simple->new(); $valid->check( $q => [ param1 => [qw/NOT_BLANK ASCII/, [qw/LENGTH 4 10/] ], ] ); $valid->check( $q => [ param2 => [qw/NOT_BLANK/], ] ); my $results = $valid->results; if ( found some error... ) { $results->set_invalid('param3' => 'MY_ERROR'); } template example [% IF results.invalid('param1') %] ... [% END %] [% IF results.invalid('param2') %] ... [% END %] [% IF results.invalid('param3', 'MY_ERROR') %] ... [% END %] =head1 HOW TO SET OPTIONS Option setting is needed by some validation, especially in plugins. You can set them in two ways. FormValidator::Simple->set_option( dbic_base_class => 'MyProj::Model::DBIC', charset => 'euc', ); or $valid = FormValidator::Simple->new( dbic_base_class => 'MyProj::Model::DBIC', charset => 'euc', ); $valid->check(...) =head1 VALIDATION COMMANDS You can use follow variety validations. and each validations can be used as negative validation with 'NOT_' prefix. FormValidator::Simple->check( $q => [ param1 => [ 'INT', ['LENGTH', 4, 10] ], param2 => [ 'NOT_INT', ['NOT_LENGTH', 4, 10] ], ] ); =over 4 =item SP check if the data has space or not. =item INT check if the data is integer or not. =item UINT unsigined integer check. for example, if -1234 is input, the validation judges it invalid. =item DECIMAL $q->param( 'num1' => '123.45678' ); my $result = FormValidator::Simple->check( $q => [ num1 => [ ['DECIMAL', 3, 5] ], ] ); each numbers (3,5) mean maximum digits before/after '.' =item ASCII check is the data consists of only ascii code. =item LENGTH check the length of the data. my $result = FormValidator::Simple->check( $q => [ param1 => [ ['LENGTH', 4] ], ] ); check if the length of the data is 4 or not. my $result = FormValidator::Simple->check( $q => [ param1 => [ ['LENGTH', 4, 10] ], ] ); when you set two arguments, it checks if the length of data is in the range between 4 and 10. =item HTTP_URL verify it is a http(s)-url my $result = FormValidator::Simple->check( $q => [ param1 => [ 'HTTP_URL' ], ] ); =item SELECTED_AT_LEAST verify the quantity of selected parameters is counted over allowed minimum. Music Movie Game my $result = FormValidator::Simple->check( $q => [ hobby => ['NOT_BLANK', ['SELECTED_AT_LEAST', 2] ], ] ); =item REGEX check with regular expression. my $result = FormValidator::Simple->check( $q => [ param1 => [ ['REGEX', qr/^hoge$/ ] ], ] ); =item DUPLICATION check if the two data are same or not. my $result = FormValidator::Simple->check( $q => [ { duplication_check => ['param1', 'param2'] } => [ 'DUPLICATION' ], ] ); =item EMAIL check with Lerror message:[% type %] - [% key %]
[% END %] [% END %] [% END %] And you can also set messages configuration before. You can prepare configuration as hash reference. FormValidator::Simple->set_messages( { action1 => { name => { NOT_BLANK => 'input name!', LENGTH => 'input name (length should be between 0 and 10)!', }, email => { DEFAULT => 'input correct email address!', }, }, } ); or a YAML file. # messages.yml DEFAULT: name: DEFAULT: name is invalid! action1: name: NOT_BLANK: input name! LENGTH: input name(length should be between 0 and 10)! email: DEFAULT: input correct email address! action2: name: DEFAULT: ... # in your perl-script, set the file's path. FormValidator::Simple->set_messages('messages.yml'); DEFAULT is a special type. If it can't find setting for indicated validation-type, it uses message set for DEFAULT. after setting, execute check(), my $result = FormValidator::Simple->check( $q => [ name => [qw/NOT_BLANK/, [qw/LENGTH 0 10/] ], email => [qw/NOT_BLANK EMAIL_LOOSE/, [qw/LENGTH 0 20/] ], ] ); # matching result and messages for indicated action. my $messages = $result->messages('action1'); foreach my $message ( @$messages ) { print $message, "\n"; } # or you can get messages as hash style. # each fieldname is the key my $field_messages = $result->field_messages('action1'); if ($field_messages->{name}) { foreach my $message ( @{ $field_messages->{name} } ) { print $message, "\n"; } } When it can't find indicated action, name, and type, it searches proper message from DEFAULT action. If in template file, [% IF result.has_error %] [% FOREACH msg IN result.messages('action1') %][% msg %]
[% END %] [% END %] you can set each message format. FormValidator::Simple->set_message_format('%s
'); my $result = FormValidator::Simple->check( $q => [ ...profile ] ); [% IF result.has_error %] [% result.messages('action1').join("\n") %] [% END %] =head1 RESULT HANDLING See L