File Coverage

blib/lib/Validator/LIVR.pm
Criterion Covered Total %
statement 129 140 92.1
branch 34 46 73.9
condition 12 18 66.6
subroutine 24 26 92.3
pod 10 11 90.9
total 209 241 86.7


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