File Coverage

blib/lib/Validate/Simple.pm
Criterion Covered Total %
statement 196 206 95.1
branch 113 122 92.6
condition 85 102 83.3
subroutine 31 32 96.8
pod 19 24 79.1
total 444 486 91.3


line stmt bran cond sub pod time code
1             package Validate::Simple;
2              
3 4     4   11377 use strict;
  4         9  
  4         120  
4 4     4   19 use warnings;
  4         8  
  4         161  
5              
6             our $VERSION = 'v0.4.1';
7              
8 4     4   24 use Carp;
  4         7  
  4         270  
9              
10 4     4   28 use Scalar::Util qw/blessed looks_like_number/;
  4         6  
  4         225  
11 4     4   1541 use Data::Types qw/:all/;
  4         4406  
  4         769  
12              
13 4     4   1843 use Data::Dumper;
  4         20425  
  4         12792  
14              
15             my $VALUES_OF_ENUM = '__ VALUES __ OF __ ENUM __';
16             my $VALIDATE_OBJECT = '__ VALIDATE __ OBJECT __';
17              
18             # Constructor
19             #
20             sub new {
21 1942     1942 1 596240 my ( $class, $specs, $all_errors ) = @_;
22              
23 1942 100 100     10809 if ( defined( $specs ) && ref( $specs ) ne 'HASH' ) {
24 1         10 croak "Specification must be a hashref";
25             }
26              
27 1941         8491 my $self = bless {
28             specs => $specs,
29             all_errors => !!$all_errors,
30             required => 0,
31             _errors => [],
32             }, $class;
33              
34 1941 100 66     8454 if ( defined( $specs ) && keys( %$specs ) ) {
35             $self->validate_specs( $specs, \$self->{required} )
36 1937 100       5773 || croak "Specs is not valid: " . join( ";\n", $self->errors() );
37             }
38              
39 1940         5147 return $self;
40             }
41              
42             # Register validation errors
43             #
44             # Pushes an error to the list of errors
45             #
46             sub _error {
47 2762     2762   8625 my ( $self, $error ) = @_;
48              
49 2762 50       5138 if ( $error ) {
50 2762         3679 push @{ $self->{_errors} }, $error;
  2762         6716  
51             }
52              
53 2762         5316 return;
54             }
55              
56              
57             # List of validatioon errors
58             #
59             sub errors {
60 7331 50   7331 1 109026 return wantarray ? @{ $_[0]->{_errors} } : $_[0]->{_errors};
  7331         20574  
61             }
62              
63             # Deletes all collected errors and returns them
64             #
65             sub delete_errors {
66 5386     5386 0 9199 my ( $self ) = @_;
67 5386         11467 my @errors = $self->errors();
68 5386         11927 $self->{_errors} = [];
69 5386         13615 return @errors;
70             }
71              
72              
73              
74             # Specifications of specification
75              
76             my @all_types = qw/
77             any
78             number
79             positive
80             non_negative
81             negative
82             non_positive
83             integer
84             positive_int
85             non_negative_int
86             negative_int
87             non_positive_int
88             string
89             array
90             hash
91             enum
92             code
93             spec
94             /;
95              
96             my @number_types = qw/
97             number
98             positive
99             non_negative
100             negative
101             non_positive
102             integer
103             positive_int
104             non_negative_int
105             negative_int
106             non_positive_int
107             /;
108              
109             my @string_types = ( 'string' );
110              
111             my @list_types = qw/
112             array
113             hash
114             /;
115              
116             my @enum_types = ( 'enum' );
117              
118             my @spec_types = ( 'spec' );
119              
120             # Common for all
121             my %any = (
122             required => {
123             type => 'any',
124             },
125             undef => {
126             type => 'any',
127             },
128             callback => {
129             type => 'code',
130             },
131             );
132              
133             # Common for numbers
134             my %number = (
135             gt => {
136             type => 'number',
137             },
138             ge => {
139             type => 'number',
140             },
141             lt => {
142             type => 'number',
143             },
144             le => {
145             type => 'number',
146             },
147             );
148              
149             # Common for strings
150             my %string = (
151             min_length => {
152             type => 'positive_int',
153             },
154             max_length => {
155             type => 'positive_int',
156             },
157             re => {
158             type => 'any',
159             callback => sub { ref( $_[0] ) eq 'Regexp' }
160             },
161             );
162              
163             # Common for lists
164             my %list = (
165             empty => {
166             type => 'any',
167             },
168             of => {
169             type => 'hash',
170             of => {
171             type => 'any',
172             },
173             required => 1,
174             },
175             );
176              
177             # Common for enums
178             my %enum = (
179             values => {
180             type => 'array',
181             of => {
182             type => 'string',
183             },
184             required => 1,
185             },
186             $VALUES_OF_ENUM => {
187             type => 'hash',
188             of => {
189             type => 'string',
190             undef => 1,
191             },
192             empty => 1,
193             },
194             );
195              
196             # Common for spec
197             my %spec = (
198             of => {
199             type => 'hash',
200             of => {
201             type => 'any',
202             },
203             required => 1,
204             empty => 0,
205             },
206             $VALIDATE_OBJECT => {
207             type => 'any',
208             callback => sub {
209             # Must be an object that implements method 'validate'
210             return blessed( $_[0] ) && $_[0]->isa(__PACKAGE__);
211             },
212             },
213             );
214              
215             # Specification of specification format
216             my %specs_of_specs = (
217             ( map { $_ => { specs => { %any } } } qw/any code/ ),
218             ( map { $_ => { specs => { %any, %number } } } @number_types ),
219             ( map { $_ => { specs => { %any, %string } } } @string_types ),
220             ( map { $_ => { specs => { %any, %list } } } @list_types ),
221             ( map { $_ => { specs => { %any, %enum } } } @enum_types ),
222             ( map { $_ => { specs => { %any, %spec } } } @spec_types ),
223             );
224              
225             for my $key ( keys %specs_of_specs ) {
226             $specs_of_specs{ $key }{specs}{type} = {
227             type => 'enum',
228             # Here we need both 'values' and "$VALUES_OF_ENUM"
229             # to pass 'validate_specs'
230             values => [ $key ],
231             $VALUES_OF_ENUM => { $key => undef },
232             required => 1,
233             };
234             my $required = 0;
235             for my $k ( keys %{ $specs_of_specs{ $key }{specs} } ) {
236             $required++
237             if exists $specs_of_specs{ $key }{specs}{ $k }{required};
238             }
239             $specs_of_specs{ $key }{required} = $required;
240             }
241              
242             # Validate functions per type
243             $specs_of_specs{any}{validate} = sub { 1; };
244             for my $type ( @all_types ) {
245             next if $type eq 'any';
246             $specs_of_specs{ $type }{validate} = sub { __PACKAGE__->$type( @_ ) };
247             }
248              
249             # Returns specification of specification
250             #
251             sub spec_of_specs {
252 0     0 0 0 return $specs_of_specs{ $_[1] };
253             }
254              
255             # Validates parameters according to specs
256             #
257             sub validate {
258 3605     3605 1 1241350 my ( $self, $params, $specs, $all_errors ) = @_;
259              
260             # Clear list of errors
261 3605         10139 $self->delete_errors();
262              
263 3605   100     14060 $all_errors //= $self->{all_errors};
264 3605         5947 $all_errors = !!$all_errors;
265 3605         5043 my $req = 0;
266             # If $specs is not passed, use the one created in constructor,
267             # and skip specs validation
268 3605 100       8954 if ( !defined( $specs ) ) {
    100          
269 1912   66     7180 $specs //= $self->{specs};
270             # If $specs is still not known
271             # do not pass
272 1912 100       3969 if ( !defined( $specs ) ) {
273 2         26 croak 'No specs passed';
274             }
275 1910         2928 $req = $self->{required};
276             }
277             # Otherwise, validate specs first
278             elsif ( !$self->validate_specs( $specs, \$req, '', $all_errors ) ) {
279 6         13 croak 'Specs is not valid: ' . join( '; ', $self->errors() );
280             }
281              
282             # If params are not HASHREF or undef
283             # do not pass
284 3597 100       7289 unless ( $self->hash( $params ) ) {
285 129         304 $self->_error( "Expected a hashref for params" );
286 129         492 return;
287             }
288              
289             # Check parameters
290 3468         8687 my $ret = $self->_validate( $params, $specs, \$req, $all_errors );
291              
292 3468         10294 return $ret;
293             }
294              
295              
296             # Validate specs against predefined specs
297             #
298             # Here we consider specs as params and validate
299             # them against rules, which are stored in %specs_of_specs
300             #
301             sub validate_specs {
302 5539     5539 0 20908 my ( $self, $specs, $req, $path_to_var, $all_errors ) = @_;
303              
304             # This variable contains path to the variable name
305 5539   100     14253 $path_to_var //= '';
306 5539   100     12879 $all_errors //= $self->{all_errors};
307 5539         7975 $all_errors = !!$all_errors;
308              
309 5539 100       10459 unless ( $self->hash( $specs ) ) {
310 4         14 $self->_error( "Specs MUST be a hashref" );
311 4         12 return;
312             }
313              
314 5535 50       11730 if ( !$req ) {
315 0         0 $self->_error( "No variable to remember amount of required params" );
316 0         0 return;
317             }
318              
319 5535 50       12220 if ( ref( $req ) ne 'SCALAR' ) {
320 0         0 $self->_error( "Scalar reference is expected" );
321             }
322              
323 5535         14487 for my $variable ( keys %$specs ) {
324 16391         24698 my $spec = $specs->{ $variable };
325 16391         28842 my $p2v = "$path_to_var/$variable";
326 16391 100       30268 unless ( $self->hash( $spec ) ) {
327 3         11 $self->_error( "Each spec MUST be a hashref: $p2v" );
328 3         9 return;
329             }
330             my $type = exists( $spec->{type} )
331             ? $spec->{type}
332 16388 100       35245 : ( $spec->{type} = 'any' );
333              
334             # Known type?
335 16388 100       32004 if ( !exists $specs_of_specs{ $type }{specs} ) {
336 3         13 $self->_error( "Unknown type '$type' in specs of $p2v" );
337 3         11 return;
338             }
339              
340              
341             # Validate spec
342 16385         22700 my $spec_of_spec = $specs_of_specs{ $type }{specs};
343 16385 100       32904 if ( !$self->_validate( $spec, $spec_of_spec, \$specs_of_specs{ $type }{required} ) ) {
344 5         21 $self->_error( "Bad spec for variable $p2v, should be " . Dumper( $spec_of_spec ) );
345 5         25 return;
346             }
347              
348             # Transform enum values into a hash
349 16380 100       30871 if ( $type eq 'enum' ) {
350             $spec->{ $VALUES_OF_ENUM }{$_} = undef
351 126         188 for @{ $spec->{values} };
  126         484  
352             }
353              
354             # Subspec
355 16380 100 66     47422 if ( $type eq 'spec' ) {
    50          
356 263         333 my $create_error;
357             my $vs = eval {
358 263         967 blessed( $self )->new( $spec->{of}, $all_errors );
359 263 100       398 } or do {
360 1   50     101 $create_error = $@ || 'Zombie error';
361             };
362 263 100       508 if ( $create_error ) {
363 1         4 $self->_error( $create_error );
364 1         3 return;
365             }
366 262         517 my @errors = $vs->delete_errors();
367 262 50       537 if ( @errors ) {
368             $self->_error( "Subspec '$p2v' is invalid: $_" )
369 0         0 for @errors;
370 0         0 return;
371             }
372 262         773 $spec->{ $VALIDATE_OBJECT } = $vs;
373             }
374             elsif ( exists( $spec->{of} ) && !$self->validate_specs( { of => $spec->{of} }, \( my $r = 0), $p2v, $all_errors ) ) {
375 0         0 $self->_error( "Bad 'of' spec for variable $p2v" );
376 0         0 return;
377             }
378              
379             # Calculate amount of rerquired params
380             $$req++
381 16379 100 100     35010 if exists( $spec->{required} ) && $spec->{required};
382             }
383              
384 5523         12471 return 1;
385             }
386              
387             # Actual validation of parameters
388             #
389             sub _validate {
390 19853     19853   36717 my ( $self, $params, $specs, $required, $all_errors ) = @_;
391              
392 19853         27632 my $req = $$required;
393 19853         42513 for my $name ( keys %$params ) {
394 33237 100       62063 if ( !exists $specs->{ $name } ) {
395 20         86 $self->_error( "Unknown param '$name':\n"
396             . Dumper($params) . "\n"
397             . Dumper($specs)
398             );
399 20 100       77 if ( $all_errors ) {
400 6         17 next;
401             }
402             else {
403 14         32 last;
404             }
405             }
406              
407 33217         48363 my $value = $params->{ $name };
408 33217         42309 my $spec = $specs->{ $name };
409             $req--
410 33217 50 66     83451 if exists( $spec->{required} ) && $spec->{required};
411              
412 33217         74601 my $valid = $self->validate_value( "/$name", $value, $spec, $all_errors );
413 33217 100 100     75779 last if !$valid && !$all_errors;
414             }
415              
416             # Not all required params found
417             # Check, what is missing
418 19853 100       36545 if ( $req ) {
419 9         26 $self->required_params( $params, $specs );
420             }
421              
422 19853 100       24992 return @{ $self->{_errors} } ? 0 : 1;
  19853         49339  
423             }
424              
425             # Checks whether all required params exist
426             #
427             sub required_params {
428 9     9 0 17 my ( $self, $params, $specs ) = @_;
429              
430 9         24 for my $par ( keys %$specs ) {
431 22         31 my $spec = $specs->{ $par };
432 22 50 66     81 if ( exists( $spec->{required} ) && $spec->{required} ) {
433 11 100       32 if ( !exists $params->{ $par } ) {
434 7         25 $self->_error( "Required param '$par' does not exist" );
435             }
436             }
437             }
438              
439 9         19 return;
440             }
441              
442             # Valdates value against spec
443             #
444             sub validate_value {
445 37578     37578 0 70314 my ( $self, $name, $value, $spec, $all_errors ) = @_;
446              
447 37578   50     71424 my $type = $spec->{type} || 'any';
448 37578   66     65714 my $undef = exists( $spec->{undef} ) && $spec->{undef};
449              
450             # If undef value is allowed and the value is undefined
451             # do not perform any furrther validation
452 37578 100 100     66295 if ( $undef && !defined( $value ) ) {
453 347         654 return 1;
454             }
455              
456 37231         51204 my @other = ();
457             # Enum
458 37231 100       66502 if ( $type eq 'enum' ) {
459 16439         27096 push @other, $spec->{ $VALUES_OF_ENUM };
460             }
461              
462             # Spec
463 37231 100       59404 if ( $type eq 'spec' ) {
464 168         281 push @other, $spec->{ $VALIDATE_OBJECT };
465             }
466              
467             # Check type
468 37231 100       69487 unless ( $specs_of_specs{ $type }{validate}->( $value, @other ) ) {
469 2103 100       11333 if ( $type eq 'spec' ) {
470             $self->_error( $name . ( /^\// ? '' : ': ' ) . $_ )
471 144 100       291 for $other[0]->errors();
472             } else {
473             my $expl = $type eq 'enum'
474 1959 100       3597 ? ( ": " . Dumper( $spec->{values} ) )
475             : '';
476 1959   100     12013 $self->_error( "$name: >> " . ( $value // '[undef]') . " << is not of type '$type'$expl" );
477             }
478 2103         4872 return;
479             }
480              
481             # Check greater than
482 35128 100       155757 if ( exists $spec->{gt} ) {
483 186 100       495 if ( $spec->{gt} >= $value ) {
484 106   50     605 $self->_error( "$name: " . ( $value // '[undef]') . " > $spec->{gt} returns false" );
485 106         268 return;
486             }
487             }
488              
489             # Check greater or equal
490 35022 100       57503 if ( exists $spec->{ge} ) {
491 162 100       445 if ( $spec->{ge} > $value ) {
492 30   50     209 $self->_error( "$name: " . ( $value // '[undef]') . " >= $spec->{ge} returns false" );
493 30         73 return;
494             }
495             }
496              
497             # Check less than
498 34992 100       57672 if ( exists $spec->{lt} ) {
499 204 100       493 if ( $spec->{lt} <= $value ) {
500 132   50     725 $self->_error( "$name: " . ( $value // '[undef]') . " < $spec->{lt} returns false" );
501 132         296 return;
502             }
503             }
504              
505             # Check less or equal
506 34860 100       53913 if ( exists $spec->{le} ) {
507 186 100       455 if ( $spec->{le} < $value ) {
508 72   50     443 $self->_error( "$name: " . ( $value // '[undef]') . " <= $spec->{le} returns false" );
509 72         173 return;
510             }
511             }
512              
513             # Check min length
514 34788 100       55985 if ( exists $spec->{min_length} ) {
515 48 100 50     172 if ( $spec->{min_length} > length( $value // '' ) ) {
516 24   50     132 $self->_error( "$name: length('" . ( $value // '[undef]') . "') > $spec->{min_length} returns false" );
517 24         68 return;
518             }
519             }
520              
521             # Check max length
522 34764 100       54701 if ( exists $spec->{max_length} ) {
523 48 100 50     184 if ( $spec->{max_length} < length( $value // '' ) ) {
524 24   50     140 $self->_error( "$name: length('" . ( $value // '[undef]') . "') < $spec->{max_length} returns false" );
525 24         53 return;
526             }
527             }
528              
529             # Check re
530 34740 100       55239 if ( exists $spec->{re} ) {
531 18 100       115 if ( $value !~ $spec->{re} ) {
532 12         68 $self->_error( "$name: '$value' does not match the Regexp $spec->{re}" );
533 12         26 return;
534             }
535             }
536              
537             # Check of
538 34728         47086 $all_errors = !!$all_errors;
539 34728 100 100     66794 if ( exists $spec->{of} && $type ne 'spec' ) {
540 2843 100       6226 if ( $type eq 'array' ) {
    50          
541 398         652 my $arr_size = $#$value;
542 398         1019 for ( my $i = 0; $i <= $arr_size; $i++ ) {
543             my $is_valid = $self->validate_value(
544             "$name/$i",
545             $value->[$i],
546             $spec->{of},
547 922         2803 $all_errors,
548             );
549 922 100 100     2699 if ( !$is_valid && !$all_errors ) {
550 86         205 return;
551             }
552             }
553             }
554             elsif ( $type eq 'hash' ) {
555 2445         5179 for my $key ( keys %$value ) {
556             my $is_valid = $self->validate_value(
557             "$name/$key",
558             $value->{$key},
559             $spec->{of},
560 3439         9287 $all_errors,
561             );
562 3439 100 100     7786 if ( !$is_valid && !$all_errors ) {
563 48         109 return;
564             }
565             }
566             }
567             else {
568 0         0 $self->_error( "$name: " . "Cannot set elements types for $type" );
569 0         0 return;
570             }
571              
572 2709 100 100     12165 if ( ( $type eq 'array' && !@$value )
      100        
      100        
573             || ( $type eq 'hash' && !%$value ) ) {
574 83 100 66     304 if ( !exists( $spec->{empty} ) || !$spec->{empty} ) {
575 63         325 $self->_error( "$name: " . ucfirst( $type ) . " cannot be empty" );
576 63         141 return;
577             }
578             }
579             }
580              
581             # Check code
582 34531 100       55472 if ( exists $spec->{callback} ) {
583 399 100       834 if ( !$spec->{callback}->( $value, @other ) ) {
584 12         84 $self->_error("Callback returned false");
585 12         29 return;
586             }
587             }
588 34519         60289 return 1;
589             }
590              
591              
592              
593             # Primitives
594             # ==========
595             sub number {
596 10473     10473 1 36644 return looks_like_number( $_[1] );
597             }
598              
599             sub positive {
600 210   100 210 1 479 return $_[0]->number( $_[1] ) && $_[1] > 0;
601             }
602              
603             sub non_negative {
604 229   100 229 1 529 return $_[0]->number( $_[1] ) && $_[1] >= 0;
605             }
606              
607             sub negative {
608 228   100 228 1 564 return $_[0]->number( $_[1] ) && $_[1] < 0;
609             }
610              
611             sub non_positive {
612 229   100 229 1 479 return $_[0]->number( $_[1] ) && $_[1] <= 0;
613             }
614              
615             sub integer {
616 866     866 1 2313 return is_int( $_[1] );
617             }
618              
619             sub positive_int {
620 677     677 1 1602 return is_count( $_[1] );
621             }
622              
623             sub non_negative_int {
624 240     240 1 739 return is_whole( $_[1] );
625             }
626              
627             sub negative_int {
628 275   100 275 1 653 return $_[0]->integer( $_[1] ) && $_[1] < 0;
629             }
630              
631             sub non_positive_int {
632 276   100 276 1 702 return $_[0]->integer( $_[1] ) && $_[1] <= 0;
633             }
634              
635             sub string {
636 17041     17041 1 34091 return is_string( $_[1] );
637             }
638              
639             sub array {
640 547     547 1 1549 return ref( $_[1] ) eq 'ARRAY';
641             }
642              
643             sub hash {
644 28133     28133 1 68284 return ref( $_[1] ) eq 'HASH';
645             }
646              
647             sub enum {
648 16445   100 16445 1 29430 return $_[0]->string( $_[1] ) && exists $_[2]->{$_[1]};
649             }
650              
651             sub code {
652 218     218 1 679 return ref( $_[1] ) eq 'CODE';
653             }
654              
655             sub spec {
656 295     295 1 684 return $_[2]->validate( $_[1] );
657             }
658              
659             1;
660              
661              
662             __END__