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   101471 use v5.10;
  4         13  
  4         139  
4 4     4   12 use strict;
  4         7  
  4         102  
5 4     4   11 use warnings FATAL => 'all';
  4         10  
  4         151  
6              
7 4     4   24 use Carp qw/croak/;
  4         8  
  4         219  
8              
9 4     4   1417 use Validator::LIVR::Rules::Common;
  4         7  
  4         91  
10 4     4   1274 use Validator::LIVR::Rules::String;
  4         5  
  4         122  
11 4     4   1301 use Validator::LIVR::Rules::Numeric;
  4         7  
  4         102  
12 4     4   1335 use Validator::LIVR::Rules::Special;
  4         11  
  4         134  
13 4     4   1400 use Validator::LIVR::Rules::Helpers;
  4         11  
  4         112  
14 4     4   1440 use Validator::LIVR::Rules::Filters;
  4         49  
  4         5059  
15              
16             our $VERSION = '0.08';
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 112203 my ($class, $livr_rules, $is_auto_trim) = @_;
60              
61 133   66     986 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         711 $self->register_rules(%DEFAULT_RULES);
71              
72 133         570 return $self;
73             }
74              
75             sub register_default_rules {
76 2     2 1 35 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         13 $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 801 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 3746 my $self = shift;
108              
109 134         139 my $all_rules = $self->{livr_rules};
110              
111 134         376 while ( my ($field, $field_rules) = each %$all_rules ) {
112 326 100       623 $field_rules = [$field_rules] if ref($field_rules) ne 'ARRAY';
113              
114 326         259 my @validators;
115 326         345 foreach my $rule (@$field_rules) {
116 396         572 my ($name, $args) = $self->_parse_rule($rule);
117 396         582 push @validators, $self->_build_validator($name, $args);
118             }
119 326         1207 $self->{validators}{$field} = \@validators;
120             }
121              
122 134         158 $self->{is_prepared} = 1;
123              
124 134         191 return $self;
125             }
126              
127             sub validate {
128 165     165 1 1231 my ($self, $data) = @_;
129 165 100       342 $self->prepare() unless $self->{is_prepared};
130              
131 165 100       337 if ( ref($data) ne 'HASH' ) {
132 2         4 $self->{errors} = 'FORMAT_ERROR';
133 2         6 return;
134             }
135              
136 163 100       283 $data = $self->_auto_trim($data) if $self->{is_auto_trim};
137              
138 163         127 my ( %errors, %result );
139              
140 163         143 foreach my $field_name ( keys %{ $self->{validators} } ) {
  163         316  
141 366         390 my $validators = $self->{validators}{$field_name};
142 366 50 33     1086 next unless $validators && @$validators;
143              
144 366         371 my $value = $data->{$field_name};
145 366         306 my $is_ok = 1;
146              
147 366         411 foreach my $v_cb (@$validators) {
148 462   100     1005 my $field_result = $result{$field_name} // $value;
149              
150 462 100       1063 my $err_code = $v_cb->(
151             exists $result{$field_name} ? $result{$field_name} : $value,
152             $data,
153             \$field_result
154             );
155              
156 462 100       1826 if ( $err_code ) {
    100          
157 119         135 $errors{$field_name} = $err_code;
158 119         102 $is_ok = 0;
159 119         181 last;
160             } elsif ( exists $data->{$field_name} ) {
161 339         691 $result{$field_name} = $field_result;
162             }
163             }
164             }
165              
166 163 100       311 if ( keys %errors ) {
167 58         72 $self->{errors} = \%errors;
168 58         139 return;
169             } else {
170 105         120 $self->{errors} = undef;
171 105         338 return \%result;
172             }
173             }
174              
175             sub get_errors {
176 88     88 1 6824 my $self = shift;
177 88         280 return $self->{errors};
178             }
179              
180             sub register_rules {
181 205     205 1 1087 my ( $self, %rules ) = @_;
182              
183 205         702 foreach my $rule_name ( keys %rules ) {
184 6036         4322 my $rule_builder = $rules{$rule_name};
185 6036 50       7311 croak "RULE_BUILDER [$rule_name] SHOULD BE A CODEREF" unless ref($rule_builder) eq 'CODE';
186              
187 6036         6940 $self->{validator_builders}{$rule_name} = $rule_builder;
188             }
189              
190 205         847 return $self;
191             }
192              
193             sub register_aliased_rule {
194 16     16 1 53 my ( $self, $alias ) = @_;
195              
196 16 50       32 die 'Alias name required' unless $alias->{name};
197 16         27 $self->{validator_builders}{ $alias->{name} } = $self->_build_aliased_rule($alias);
198              
199 16         19 return $self;
200             }
201              
202             sub get_rules {
203 397     397 1 2369 my $self = shift;
204              
205 397         301 return { %{$self->{validator_builders}} };
  397         3765  
206             }
207              
208             sub _parse_rule {
209 396     396   360 my ($self, $livr_rule) = @_;
210              
211 396         282 my ($name, $args);
212              
213 396 100       553 if ( ref($livr_rule) eq 'HASH' ) {
214 195         343 ($name) = keys %$livr_rule;
215              
216 195         252 $args = $livr_rule->{$name};
217 195 100       373 $args = [$args] unless ref($args) eq 'ARRAY';
218             } else {
219 201         168 $name = $livr_rule;
220 201         260 $args = [];
221             }
222              
223 396         584 return ($name, $args);
224             }
225              
226             sub _build_validator {
227 396     396   387 my ($self, $name, $args) = @_;
228 396 50       694 die "Rule [$name] not registered\n" unless $self->{validator_builders}->{$name};
229              
230 396         519 return $self->{validator_builders}->{$name}->( @$args, $self->get_rules() );
231             }
232              
233             sub _build_aliased_rule {
234 16     16   17 my ($class, $alias) = @_;
235              
236 16 50       25 die 'Alias name required' unless $alias->{name};
237 16 50       37 die 'Alias rules required' unless $alias->{rules};
238              
239 16         29 my $livr = { value => $alias->{rules} };
240              
241             return sub {
242 32     32   29 my $rule_builders = shift;
243 32         56 my $validator = __PACKAGE__->new($livr)->register_rules(%$rule_builders)->prepare();
244              
245             return sub {
246 32         33 my ($value, $params, $output_ref) = @_;
247              
248 32         68 my $result = $validator->validate( { value => $value } );
249              
250 32 100       51 if ( $result ) {
251 21         19 $$output_ref = $result->{value};
252 21         28 return;
253             } else {
254 11   66     31 return $alias->{error} || $validator->get_errors()->{value};
255             }
256 32         268 };
257 16         63 };
258             }
259              
260             sub _auto_trim {
261 10     10   16 my ( $self, $data ) = @_;
262 10         8 my $ref_type = ref($data);
263              
264 10 100 66     37 if ( !$ref_type && $data ) {
    50          
    0          
265 6         16 $data =~ s/^\s+//;
266 6         42 $data =~ s/\s+$//;
267 6         17 return $data;
268             }
269             elsif ( $ref_type eq 'HASH' ) {
270 4         5 my $trimmed_data = {};
271              
272 4         9 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