File Coverage

blib/lib/Validator/LIVR.pm
Criterion Covered Total %
statement 129 140 92.1
branch 32 44 72.7
condition 10 15 66.6
subroutine 24 26 92.3
pod 10 11 90.9
total 205 236 86.8


line stmt bran cond sub pod time code
1             package Validator::LIVR;
2              
3 4     4   135288 use v5.10;
  4         15  
  4         422  
4 4     4   19 use strict;
  4         7  
  4         166  
5 4     4   20 use warnings FATAL => 'all';
  4         10  
  4         212  
6              
7 4     4   26 use Carp qw/croak/;
  4         14  
  4         350  
8              
9 4     4   1842 use Validator::LIVR::Rules::Common;
  4         10  
  4         123  
10 4     4   1711 use Validator::LIVR::Rules::String;
  4         11  
  4         140  
11 4     4   1948 use Validator::LIVR::Rules::Numeric;
  4         10  
  4         151  
12 4     4   1920 use Validator::LIVR::Rules::Special;
  4         10  
  4         139  
13 4     4   1701 use Validator::LIVR::Rules::Helpers;
  4         14  
  4         143  
14 4     4   1869 use Validator::LIVR::Rules::Filters;
  4         71  
  4         6744  
15              
16             our $VERSION = '0.09';
17              
18             my %DEFAULT_RULES = (
19             'required' => \&Validator::LIVR::Rules::Common::required,
20             'not_empty' => \&Validator::LIVR::Rules::Common::not_empty,
21             'not_empty_list' => \&Validator::LIVR::Rules::Common::not_empty_list,
22              
23              
24             'one_of' => \&Validator::LIVR::Rules::String::one_of,
25             'min_length' => \&Validator::LIVR::Rules::String::min_length,
26             'max_length' => \&Validator::LIVR::Rules::String::max_length,
27             'length_equal' => \&Validator::LIVR::Rules::String::length_equal,
28             'length_between' => \&Validator::LIVR::Rules::String::length_between,
29             'like' => \&Validator::LIVR::Rules::String::like,
30              
31             'integer' => \&Validator::LIVR::Rules::Numeric::integer,
32             'positive_integer' => \&Validator::LIVR::Rules::Numeric::positive_integer,
33             'decimal' => \&Validator::LIVR::Rules::Numeric::decimal,
34             'positive_decimal' => \&Validator::LIVR::Rules::Numeric::positive_decimal,
35             'max_number' => \&Validator::LIVR::Rules::Numeric::max_number,
36             'min_number' => \&Validator::LIVR::Rules::Numeric::min_number,
37             'number_between' => \&Validator::LIVR::Rules::Numeric::number_between,
38              
39             'email' => \&Validator::LIVR::Rules::Special::email,
40             'equal_to_field' => \&Validator::LIVR::Rules::Special::equal_to_field,
41             'url' => \&Validator::LIVR::Rules::Special::url,
42             'iso_date' => \&Validator::LIVR::Rules::Special::iso_date,
43              
44             'nested_object' => \&Validator::LIVR::Rules::Helpers::nested_object,
45             'list_of' => \&Validator::LIVR::Rules::Helpers::list_of,
46             'list_of_objects' => \&Validator::LIVR::Rules::Helpers::list_of_objects,
47             'list_of_different_objects' => \&Validator::LIVR::Rules::Helpers::list_of_different_objects,
48              
49             'trim' => \&Validator::LIVR::Rules::Filters::trim,
50             'to_lc' => \&Validator::LIVR::Rules::Filters::to_lc,
51             'to_uc' => \&Validator::LIVR::Rules::Filters::to_uc,
52             'remove' => \&Validator::LIVR::Rules::Filters::remove,
53             'leave_only' => \&Validator::LIVR::Rules::Filters::leave_only,
54             );
55              
56             my $IS_DEFAULT_AUTO_TRIM = 0;
57              
58             sub new {
59 133     133 1 96825 my ($class, $livr_rules, $is_auto_trim) = @_;
60              
61 133   66     893 my $self = bless {
62             is_prepared => 0,
63             livr_rules => $livr_rules,
64             validators => {},
65             validator_builders => {},
66             errors => undef,
67             is_auto_trim => ( $is_auto_trim // $IS_DEFAULT_AUTO_TRIM )
68             }, $class;
69              
70 133         668 $self->register_rules(%DEFAULT_RULES);
71              
72 133         522 return $self;
73             }
74              
75             sub register_default_rules {
76 2     2 1 36 my ( $class, %rules ) = @_;
77              
78 2         7 foreach my $rule_name ( keys %rules ) {
79 4         6 my $rule_builder = $rules{$rule_name};
80 4 50       11 croak "RULE_BUILDER [$rule_name] SHOULD BE A CODEREF" unless ref($rule_builder) eq 'CODE';
81              
82 4         11 $DEFAULT_RULES{$rule_name} = $rule_builder;
83             }
84              
85 2         6 return $class;
86             }
87              
88             sub register_aliased_default_rule {
89 0     0 1 0 my ( $class, $alias ) = @_;
90              
91 0 0       0 die 'Alias name required' unless $alias->{name};
92 0         0 $DEFAULT_RULES{ $alias->{name} } = $class->_build_aliased_rule($alias);
93              
94 0         0 return $class;
95             }
96              
97             sub get_default_rules {
98 1     1 1 703 return {%DEFAULT_RULES};
99             }
100              
101             sub default_auto_trim {
102 0     0 1 0 my ($class, $is_auto_trim) = @_;
103 0         0 $IS_DEFAULT_AUTO_TRIM = !!$is_auto_trim;
104             }
105              
106             sub prepare {
107 134     134 0 3763 my $self = shift;
108              
109 134         129 my $all_rules = $self->{livr_rules};
110              
111 134         342 while ( my ($field, $field_rules) = each %$all_rules ) {
112 330 100       663 $field_rules = [$field_rules] if ref($field_rules) ne 'ARRAY';
113              
114 330         250 my @validators;
115 330         346 foreach my $rule (@$field_rules) {
116 400         545 my ($name, $args) = $self->_parse_rule($rule);
117 400         558 push @validators, $self->_build_validator($name, $args);
118             }
119 330         1144 $self->{validators}{$field} = \@validators;
120             }
121              
122 134         130 $self->{is_prepared} = 1;
123              
124 134         180 return $self;
125             }
126              
127             sub validate {
128 165     165 1 1159 my ($self, $data) = @_;
129 165 100       307 $self->prepare() unless $self->{is_prepared};
130              
131 165 100       256 if ( ref($data) ne 'HASH' ) {
132 1         2 $self->{errors} = 'FORMAT_ERROR';
133 1         4 return;
134             }
135              
136 164 100       283 $data = $self->_auto_trim($data) if $self->{is_auto_trim};
137              
138 164         113 my ( %errors, %result );
139              
140 164         123 foreach my $field_name ( keys %{ $self->{validators} } ) {
  164         281  
141 370         345 my $validators = $self->{validators}{$field_name};
142 370 50 33     1061 next unless $validators && @$validators;
143              
144 370         322 my $value = $data->{$field_name};
145 370         261 my $is_ok = 1;
146              
147 370         338 foreach my $v_cb (@$validators) {
148 466   100     985 my $field_result = $result{$field_name} // $value;
149              
150 466 100       1091 my $err_code = $v_cb->(
151             exists $result{$field_name} ? $result{$field_name} : $value,
152             $data,
153             \$field_result
154             );
155              
156 466 100       1702 if ( $err_code ) {
    100          
157 122         129 $errors{$field_name} = $err_code;
158 122         95 $is_ok = 0;
159 122         177 last;
160             } elsif ( exists $data->{$field_name} ) {
161 340         711 $result{$field_name} = $field_result;
162             }
163             }
164             }
165              
166 164 100       280 if ( keys %errors ) {
167 59         70 $self->{errors} = \%errors;
168 59         131 return;
169             } else {
170 105         99 $self->{errors} = undef;
171 105         232 return \%result;
172             }
173             }
174              
175             sub get_errors {
176 87     87 1 6363 my $self = shift;
177 87         263 return $self->{errors};
178             }
179              
180             sub register_rules {
181 205     205 1 1125 my ( $self, %rules ) = @_;
182              
183 205         654 foreach my $rule_name ( keys %rules ) {
184 6036         4189 my $rule_builder = $rules{$rule_name};
185 6036 50       7198 croak "RULE_BUILDER [$rule_name] SHOULD BE A CODEREF" unless ref($rule_builder) eq 'CODE';
186              
187 6036         6427 $self->{validator_builders}{$rule_name} = $rule_builder;
188             }
189              
190 205         771 return $self;
191             }
192              
193             sub register_aliased_rule {
194 16     16 1 51 my ( $self, $alias ) = @_;
195              
196 16 50       31 die 'Alias name required' unless $alias->{name};
197 16         22 $self->{validator_builders}{ $alias->{name} } = $self->_build_aliased_rule($alias);
198              
199 16         24 return $self;
200             }
201              
202             sub get_rules {
203 401     401 1 2111 my $self = shift;
204              
205 401         298 return { %{$self->{validator_builders}} };
  401         3652  
206             }
207              
208             sub _parse_rule {
209 400     400   378 my ($self, $livr_rule) = @_;
210              
211 400         261 my ($name, $args);
212              
213 400 100       521 if ( ref($livr_rule) eq 'HASH' ) {
214 199         309 ($name) = keys %$livr_rule;
215              
216 199         243 $args = $livr_rule->{$name};
217 199 100       363 $args = [$args] unless ref($args) eq 'ARRAY';
218             } else {
219 201         174 $name = $livr_rule;
220 201         255 $args = [];
221             }
222              
223 400         529 return ($name, $args);
224             }
225              
226             sub _build_validator {
227 400     400   414 my ($self, $name, $args) = @_;
228 400 50       680 die "Rule [$name] not registered\n" unless $self->{validator_builders}->{$name};
229              
230 400         482 return $self->{validator_builders}->{$name}->( @$args, $self->get_rules() );
231             }
232              
233             sub _build_aliased_rule {
234 16     16   14 my ($class, $alias) = @_;
235              
236 16 50       22 die 'Alias name required' unless $alias->{name};
237 16 50       38 die 'Alias rules required' unless $alias->{rules};
238              
239 16         25 my $livr = { value => $alias->{rules} };
240              
241             return sub {
242 32     32   31 my $rule_builders = shift;
243 32         55 my $validator = __PACKAGE__->new($livr)->register_rules(%$rule_builders)->prepare();
244              
245             return sub {
246 32         34 my ($value, $params, $output_ref) = @_;
247              
248 32         58 my $result = $validator->validate( { value => $value } );
249              
250 32 100       45 if ( $result ) {
251 21         24 $$output_ref = $result->{value};
252 21         23 return;
253             } else {
254 11   66     31 return $alias->{error} || $validator->get_errors()->{value};
255             }
256 32         232 };
257 16         68 };
258             }
259              
260             sub _auto_trim {
261 10     10   11 my ( $self, $data ) = @_;
262 10         11 my $ref_type = ref($data);
263              
264 10 100 66     27 if ( !$ref_type && $data ) {
    50          
    0          
265 6         17 $data =~ s/^\s+//;
266 6         14 $data =~ s/\s+$//;
267 6         13 return $data;
268             }
269             elsif ( $ref_type eq 'HASH' ) {
270 4         5 my $trimmed_data = {};
271              
272 4         8 foreach my $key ( keys %$data ) {
273 8         17 $trimmed_data->{$key} = $self->_auto_trim( $data->{$key} );
274             }
275              
276 4         7 return $trimmed_data;
277             }
278             elsif ( $ref_type eq 'ARRAY' ) {
279 0           my $trimmed_data = [];
280              
281 0           for ( my $i = 0; $i < @$data; $i++ ) {
282 0           $trimmed_data->[$i] = $self->_auto_trim( $data->[$i] )
283             }
284              
285 0           return $trimmed_data;
286             }
287              
288 0           return $data;
289             }
290              
291             1; # End of Validator::LIVR
292              
293             =for HTML
294              
295             =head1 NAME
296              
297             Validator::LIVR - Lightweight validator supporting Language Independent Validation Rules Specification (LIVR)
298              
299             =head1 SYNOPSIS
300              
301             # Common usage
302             Validator::LIVR->default_auto_trim(1);
303              
304             my $validator = Validator::LIVR->new({
305             name => 'required',
306             email => [ 'required', 'email' ],
307             gender => { one_of => ['male', 'female'] },
308             phone => { max_length => 10 },
309             password => [ 'required', {min_length => 10} ],
310             password2 => { equal_to_field => 'password' }
311             });
312              
313             if ( my $valid_data = $validator->validate($user_data) ) {
314             save_user($valid_data);
315             } else {
316             my $errors = $validator->get_errors();
317             ...
318             }
319              
320             # You can use filters separately or can combine them with validation:
321             my $validator = Validator::LIVR->new({
322             email => [ 'required', 'trim', 'email', 'to_lc' ]
323             });
324              
325             # Feel free to register your own rules
326             # You can use aliases(prefferable, syntax covered by the specification) for a lot of cases:
327              
328             my $validator = Validator::LIVR->new({
329             password => ['required', 'strong_password']
330             });
331              
332             $validator->register_aliased_rule({
333             name => 'strong_password',
334             rules => {min_length => 6},
335             error => 'WEAK_PASSWORD'
336             });
337              
338             # or you can write more sophisticated rules directly
339              
340             my $validator = Validator::LIVR->new({
341             password => ['required', 'strong_password']
342             });
343              
344             $validator->register_rules( 'strong_password' => sub {
345             return sub {
346             my $value = shift;
347              
348             # We already have "required" rule to check that the value is present
349             return if !defined($value) || $value eq '';
350              
351             return 'WEAK_PASSWORD' if length($value) < 6;
352             return;
353             }
354             } );
355              
356              
357             # If you want to stop on the first error
358             # you can overwrite all rules with your own which use exceptions
359             my $default_rules = Validator::LIVR->ger_default_rules();
360              
361             while ( my ($rule_name, $rule_builder) = each %$default_rules ) {
362             Validator::LIVR->register_default_rules($rule_name => sub {
363             my $rule_validator = $rule_builder->(@_);
364              
365             return sub {
366             my $error = $rule_validator->(@_);
367             die $error if $error;
368             return;
369             }
370             });
371             }
372              
373             =head1 DESCRIPTION
374              
375             L lightweight validator supporting Language Independent Validation Rules Specification (LIVR)
376              
377             See L for rules documentation.
378              
379             Features:
380              
381             =over 4
382              
383             =item * Rules are declarative and language independent
384              
385             =item * Any number of rules for each field
386              
387             =item * Return together errors for all fields
388              
389             =item * Excludes all fields that do not have validation rules described
390              
391             =item * Has possibility to validatate complex hierarchical structures
392              
393             =item * Easy to describe and undersand rules
394              
395             =item * Returns understandable error codes(not error messages)
396              
397             =item * Easy to add own rules
398              
399             =item * Multipurpose (user input validation, configs validation, contracts programming etc)
400              
401             =back
402              
403             =head1 CLASS METHODS
404              
405             =head2 Validator::LIVR->new( $LIVR [, $IS_AUTO_TRIM] )
406              
407             Contructor creates validator objects.
408              
409             $LIVR - validations rules. Rules description is available here - L
410              
411             $IS_AUTO_TRIM - asks validator to trim all values before validation. Output will be also trimmed.
412             if $IS_AUTO_TRIM is undef than default_auto_trim value will be used.
413              
414             =head2 Validator::LIVR->register_aliased_default_rule( $ALIAS )
415              
416             $ALIAS - is a hash that contains: name, rules, error (optional).
417              
418             Validator::LIVR->register_aliased_default_rule({
419             name => 'valid_address',
420             rules => { nested_object => {
421             country => 'required',
422             city => 'required',
423             zip => 'positive_integer'
424             }}
425             });
426              
427             Then you can use "valid\_address" for validation:
428              
429             {
430             address => 'valid_address'
431             }
432              
433              
434             You can register aliases with own errors:
435              
436             Validator::LIVR->register_aliased_default_rule({
437             name => 'adult_age'
438             rules => [ 'positive_integer', { min_number => 18 } ],
439             error => 'WRONG_AGE'
440             });
441              
442             All rules/aliases for the validator are equal. The validator does not distinguish "required", "list\_of\_different\_objects" and "trim" rules. So, you can extend validator with any rules/alias you like.
443              
444              
445             =head2 Validator::LIVR->register_default_rules( RULE_NAME => \&RULE_BUILDER, ... )
446              
447             &RULE_BUILDER - is a subtorutine reference which will be called for building single rule validator.
448              
449              
450             Validator::LIVR->register_default_rules( my_rule => sub {
451             my ($arg1, $arg2, $arg3, $rule_builders) = @_;
452              
453             # $rule_builders - are rules from original validator
454             # to allow you create new validator with all supported rules
455             # my $validator = Validator::LIVR->new($livr)->register_rules(%$rule_builders)->prepare();
456              
457             return sub {
458             my ( $value, $all_values, $output_ref ) = @_;
459              
460             if ($not_valid) {
461             return "SOME_ERROR_CODE"
462             }
463             else {
464              
465             }
466              
467             }
468             });
469              
470             Then you can use "my_rule" for validation:
471              
472             {
473             name1 => 'my_rule' # Call without parameters
474             name2 => { 'my_rule' => $arg1 } # Call with one parameter.
475             name3 => { 'my_rule' => [$arg1] } # Call with one parameter.
476             name4 => { 'my_rule' => [ $arg1, $arg2, $arg3 ] } # Call with many parameters.
477             }
478              
479              
480             Here is "max_number" implemenation:
481              
482             sub max_number {
483             my $max_number = shift;
484              
485             return sub {
486             my $value = shift;
487              
488             # We do not validate empty fields. We have "required" rule for this purpose
489             return if !defined($value) || $value eq '';
490              
491             return 'TOO_HIGH' if $value > $max_number; # Return error message
492             return; # returning undef means that there was no errors;
493             };
494             }
495              
496             Validator::LIVR->register_default_rules( max_number => \&max_number );
497              
498             All rules for the validator are equal. It does not distinguish "required", "list_of_different_objects" and "trim" rules.
499             So, you can extend validator with any rules you like.
500              
501             Just look at the existing rules implementation:
502              
503             =over 4
504              
505             =item * L
506              
507             =item * L;
508              
509             =item * L;
510              
511             =item * L;
512              
513             =item * L;
514              
515             =item * L;
516              
517             =back
518              
519             All rules description is available here - L
520              
521              
522             =head2 Validator::LIVR->get_default_rules( )
523              
524             returns hashref containing all default rule_builders for the validator.
525             You can register new rule or update existing one with "register_rules" method.
526              
527             =head2 Validator::LIVR->default_auto_trim($IS_AUTO_TRIM)
528              
529             Enables or disables automatic trim for input data. If is on then every new validator instance will have auto trim option enabled
530              
531             =head1 OBJECT METHODS
532              
533             =head2 $VALIDATOR->validate(\%INPUT)
534              
535             Validates user input. On success returns $VALID_DATA (contains only data that has described validation rules). On error return false.
536              
537             my $VALID_DATA = $VALIDATOR->validate(\%INPUT)
538              
539             if ($VALID_DATA) {
540              
541             } else {
542             my $errors = $VALIDATOR->get_errors();
543             }
544              
545             =head2 $VALIDATOR->get_errors( )
546              
547             Returns errors hash.
548              
549             {
550             "field1" => "ERROR_CODE",
551             "field2" => "ERROR_CODE",
552             ...
553             }
554              
555             For example:
556              
557             {
558             "country" => "NOT_ALLOWED_VALUE",
559             "zip" => "NOT_POSITIVE_INTEGER",
560             "street" => "REQUIRED",
561             "building" => "NOT_POSITIVE_INTEGER"
562             },
563              
564             =head2 $VALIDATOR->register_rules( RULE_NAME => \&RULE_BUILDER, ... )
565              
566             &RULE_BUILDER - is a subtorutine reference which will be called for building single rule validator.
567              
568             See "Validator::LIVR->register_default_rules" for rules examples.
569              
570             =head2 $VALIDATOR->register_aliased_rule( $ALIAS )
571              
572             $ALIAS - is a composite validation rule.
573              
574             See "Validator::LIVR->register_aliased_default_rule" for rules examples.
575              
576             =head2 $VALIDATOR->get_rules( )
577              
578             returns hashref containing all rule_builders for the validator. You can register new rule or update existing one with "register_rules" method.
579              
580             =head1 AUTHOR
581              
582             Viktor Turskyi, C<< >>
583              
584             =head1 BUGS
585              
586             Please report any bugs or feature requests to Github L
587              
588              
589             =head1 SUPPORT
590              
591             You can find documentation for this module with the perldoc command.
592              
593             perldoc Validator::LIVR
594              
595              
596             You can also look for information at:
597              
598             =over 4
599              
600             =item * RT: CPAN's request tracker (report bugs here)
601              
602             L
603              
604             =item * AnnoCPAN: Annotated CPAN documentation
605              
606             L
607              
608             =item * CPAN Ratings
609              
610             L
611              
612             =item * Search CPAN
613              
614             L
615              
616             =back
617              
618              
619             =head1 ACKNOWLEDGEMENTS
620              
621              
622             =head1 LICENSE AND COPYRIGHT
623              
624             Copyright 2012 Viktor Turskyi.
625              
626             This program is free software; you can redistribute it and/or modify it
627             under the terms of the the Artistic License (2.0). You may obtain a
628             copy of the full license at:
629              
630             L
631              
632             Any use, modification, and distribution of the Standard or Modified
633             Versions is governed by this Artistic License. By using, modifying or
634             distributing the Package, you accept this license. Do not use, modify,
635             or distribute the Package, if you do not accept this license.
636              
637             If your Modified Version has been derived from a Modified Version made
638             by someone other than you, you are nevertheless required to ensure that
639             your Modified Version complies with the requirements of this license.
640              
641             This license does not grant you the right to use any trademark, service
642             mark, tradename, or logo of the Copyright Holder.
643              
644             This license includes the non-exclusive, worldwide, free-of-charge
645             patent license to make, have made, use, offer to sell, sell, import and
646             otherwise transfer the Package with respect to any patent claims
647             licensable by the Copyright Holder that are necessarily infringed by the
648             Package. If you institute patent litigation (including a cross-claim or
649             counterclaim) against any party alleging that the Package constitutes
650             direct or contributory patent infringement, then this Artistic License
651             to you shall terminate on the date that such litigation is filed.
652              
653             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
654             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
655             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
656             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
657             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
658             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
659             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
660             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
661              
662              
663             =cut
664              
665