File Coverage

blib/lib/Validate/Simple.pm
Criterion Covered Total %
statement 162 170 95.2
branch 83 90 92.2
condition 53 70 75.7
subroutine 31 32 96.8
pod 18 24 75.0
total 347 386 89.9


line stmt bran cond sub pod time code
1             package Validate::Simple;
2              
3 3     3   7379 use strict;
  3         5  
  3         76  
4 3     3   13 use warnings;
  3         6  
  3         107  
5              
6             our $VERSION = '0.02';
7              
8 3     3   14 use Carp;
  3         5  
  3         170  
9              
10 3     3   16 use Scalar::Util qw/looks_like_number/;
  3         4  
  3         124  
11 3     3   862 use Data::Types qw/:all/;
  3         2576  
  3         490  
12              
13 3     3   1538 use Data::Dumper;
  3         17453  
  3         6026  
14              
15             my $VALUES_OF_ENUM = '__ VALUES __ OF __ ENUM __';
16              
17             # Constructor
18             #
19             sub new {
20 489     489 1 157660 my ( $class, $specs ) = @_;
21              
22 489 50 66     2465 if ( defined $specs && ref $specs ne 'HASH' ) {
23 0         0 croak "Specification must be a hashref";
24             }
25              
26 489         1449 my $self = bless {
27             specs => $specs,
28             _errors => [],
29             }, $class;
30              
31 489 100 66     2135 if ( defined $specs && keys %$specs ) {
32 486         1131 $self->validate_specs( $specs );
33             }
34              
35 489         1200 return $self;
36             }
37              
38             # Registe validation error
39             #
40             # Pushes an error to the list of errors
41             #
42             sub _error {
43 2177     2177   3914 my ( $self, $error ) = @_;
44              
45 2177 100       4002 if ( $error ) {
46 709         1048 push @{ $self->{_errors} }, $error;
  709         1769  
47             }
48              
49 2177         3704 return;
50             }
51              
52              
53             # List of validatioon errors
54             #
55             sub errors {
56 486 50   486 1 988 return wantarray ? @{ $_[0]->{_errors} } : $_[0]->{_errors};
  486         2089  
57             }
58              
59             # Deletes all collected errors and returns them
60             #
61             sub delete_errors {
62 1468     1468 0 2387 my ( $self ) = @_;
63 1468         2997 my @errors = $self->_error();
64 1468         3183 $self->{_errors} = [];
65 1468         3390 return @errors;
66             }
67              
68              
69              
70             # Specifications of specification
71              
72             my @all_types = qw/
73             any
74             number
75             positive
76             non_negative
77             negative
78             non_positive
79             integer
80             positive_int
81             non_negative_int
82             negative_int
83             non_positive_int
84             string
85             array
86             hash
87             enum
88             code
89             /;
90              
91             my @number_types = qw/
92             number
93             positive
94             non_negative
95             negative
96             non_positive
97             integer
98             positive_int
99             non_negative_int
100             negative_int
101             non_positive_int
102             /;
103              
104             my @string_types = ( 'string' );
105              
106             my @list_types = qw/
107             array
108             hash
109             /;
110              
111             my @enum_types = ( 'enum' );
112              
113             # Common for all
114             my %any = (
115             required => {
116             type => 'any',
117             },
118             undef => {
119             type => 'any',
120             },
121             callback => {
122             type => 'code',
123             },
124             );
125              
126             # Common for numbers
127             my %number = (
128             gt => {
129             type => 'number',
130             },
131             ge => {
132             type => 'number',
133             },
134             lt => {
135             type => 'number',
136             },
137             le => {
138             type => 'number',
139             },
140             );
141              
142             # Common for strings
143             my %string = (
144             min_length => {
145             type => 'positive_int',
146             },
147             max_length => {
148             type => 'positive_int',
149             },
150             );
151              
152             # Common for lists
153             my %list = (
154             empty => {
155             type => 'any',
156             },
157             of => {
158             type => 'hash',
159             of => {
160             type => 'any',
161             },
162             required => 1,
163             },
164             );
165              
166             # Common for enums
167             my %enum = (
168             values => {
169             type => 'array',
170             of => {
171             type => 'string',
172             },
173             required => 1,
174             },
175             $VALUES_OF_ENUM => {
176             type => 'hash',
177             of => {
178             type => 'string',
179             undef => 1,
180             },
181             empty => 1,
182             },
183             );
184              
185             # Specification of specification format
186             my %specs_of_specs = (
187             any => { %any },
188             ( map { $_ => { %any, %number } } @number_types ),
189             ( map { $_ => { %any, %string } } @string_types ),
190             ( map { $_ => { %any, %list } } @list_types ),
191             ( map { $_ => { %any, %enum } } @enum_types ),
192             );
193              
194             for my $key ( keys %specs_of_specs ) {
195             $specs_of_specs{ $key }{type} = {
196             type => 'enum',
197             values => [ $key ],
198             required => 1,
199             };
200             }
201              
202             # Validate functions per type
203             my %validate = ( any => sub { 1; } );
204             for my $type ( @all_types ) {
205             next if $type eq 'any';
206             $validate{ $type } = sub { __PACKAGE__->$type( @_ ) };
207             }
208              
209             # Returns specification of specification
210             #
211             sub spec_of_specs {
212 0     0 0 0 return $specs_of_specs{ $_[1] };
213             }
214              
215             # Validates parameters according to specs
216             #
217             sub validate {
218 976     976 1 323881 my ( $self, $params, $specs ) = @_;
219              
220             # If $specs is not passed, use the one created in constructor,
221             # and skip specs validation
222 976         1859 my $skip_specs_validation = 1;
223 976 100       2609 if ( !defined $specs ) {
224 490   66     2267 $specs //= $self->{specs};
225 490         706 $skip_specs_validation = 0;
226             }
227              
228             # If specs are not known
229             # do not pass
230 976 100       2095 return if !defined $specs;
231              
232             # Clear list of errors
233 972         2751 $self->delete_errors();
234              
235             # If params are not HASHREF or undef
236             # do not pass
237 972 100       1897 unless ( $self->hash( $params ) ) {
238 2         5 $self->_error("Expected a hashref for params");
239 2         14 return;
240             }
241              
242             # Check parameters
243 970         2318 my $ret = $self->_validate( $params, $specs, $skip_specs_validation );
244              
245 970         3225 return $ret;
246             }
247              
248              
249             # Validate specs against predefined specs
250             #
251             # Here we consider specs as params and validate
252             # them against rules, which are stored in %specs_of_specs
253             #
254             sub validate_specs {
255 1479     1479 0 5067 my ( $self, $specs, $path_to_var ) = @_;
256              
257             # This variable contains path to the variable name
258 1479   100     4228 $path_to_var //= '';
259              
260 1479 100       3100 unless ( $self->hash( $specs ) ) {
261 2         6 $self->_error( "Specs MUST be a hashref" );
262 2         8 return;
263             }
264              
265 1477         4472 while ( my ( $variable, $spec ) = each %$specs ) {
266 4941         9194 my $p2v = "$path_to_var/$variable";
267 4941 100       7909 unless ( $self->hash( $spec ) ) {
268 2         5 $self->_error( "Each spec MUST be a hashref: $p2v" );
269 2         6 return;
270             }
271             my $type = exists $spec->{type}
272             ? $spec->{type}
273 4939 100       10577 : ( $spec->{type} = 'any' );
274              
275             # Known type?
276 4939 100       8681 if ( !exists $specs_of_specs{ $type } ) {
277 1         4 $self->_error( "Unknown type '$type' in specs of $p2v" );
278 1         4 return;
279             }
280              
281             # Validate spec
282 4938         7031 my $spec_of_spec = $specs_of_specs{ $type };
283 4938 100       8521 if ( !$self->_validate( $spec, $spec_of_spec, 'skip_specs_validation' ) ) {
284 3         9 $self->_error( "Bad spec for variable $p2v, should be " . Dumper( $spec_of_spec ) );
285 3         13 return;
286             }
287              
288             # Check spec of 'of'
289 4935 50 66     15782 if ( exists $spec->{of} && !$self->validate_specs( { of => $spec->{of} }, $p2v ) ) {
290 0         0 return;
291             }
292             }
293              
294 1471         4577 return 1;
295             }
296              
297             # Actual validation of parameters
298             #
299             sub _validate {
300 5908     5908   9915 my ( $self, $params, $specs, $skip_specs_validation ) = @_;
301              
302             # Check mandatory params
303             return
304 5908 100       9935 unless $self->required_params( $params, $specs );
305              
306             # Check unknown params
307             return
308 5905 100       10863 unless $self->unknown_params( $params, $specs );
309              
310 5902 50 66     10807 if ( !$skip_specs_validation && !$self->validate_specs( $specs ) ) {
311 0         0 return;
312             }
313              
314 5902         14282 while( my ( $name, $value ) = each %$params ) {
315 9923 50       17150 if ( !exists $specs->{ $name } ) {
316 0         0 $self->_error( "Can't find specs for $name" );
317 0         0 return;
318             }
319 9923         12979 my $spec = $specs->{ $name };
320              
321 9923         16135 my $valid = $self->validate_value( $value, $spec );
322 9923 100       31328 return unless $valid;
323             }
324              
325 5209         10049 return 1;
326             }
327              
328             # Checks whether all required params exist
329             #
330             sub required_params {
331 5908     5908 0 8802 my ( $self, $params, $specs ) = @_;
332              
333 5908         7396 my $ret = 1;
334 5908         15331 for my $par ( keys %$specs ) {
335 41286         52588 my $spec = $specs->{ $par };
336 41286 50 66     76487 if ( exists $spec->{required} && $spec->{required} ) {
337 5490 100       11102 if ( !exists $params->{ $par } ) {
338 3         16 $self->_error( "Required param '$par' does not exist" );
339 3         5 $ret = 0;
340             }
341             }
342             }
343              
344 5908         12981 return $ret;
345             }
346              
347             # Check whether unknown params exist
348             #
349             sub unknown_params {
350 5905     5905 0 9204 my ( $self, $params, $specs ) = @_;
351              
352 5905         7505 my $ret = 1;
353 5905         10836 for my $par ( keys %$params ) {
354 9929 100       17658 if ( !exists $specs->{ $par } ) {
355 3         11 $self->_error("Unknown param '$par'");
356 3         6 $ret = 0;
357             }
358             }
359              
360 5905         11197 return $ret;
361             }
362              
363              
364             # Valdates value against spec
365             #
366             sub validate_value {
367 10841     10841 0 17351 my ( $self, $value, $spec ) = @_;
368              
369 10841   50     19240 my $type = $spec->{type} || 'any';
370 10841   66     19704 my $undef = exists $spec->{undef} && $spec->{undef};
371              
372             # If undef value is allowed and the value is undefined
373             # do not perform any furrther validation
374 10841 100 100     19428 if ( $undef && !defined $value ) {
375 78         184 return 1;
376             }
377              
378 10763         13996 my @other = ();
379             # Enum
380 10763 100       18772 if ( $type eq 'enum' ) {
381             # Create a hash
382 4954 100       8539 if ( !exists $spec->{ $VALUES_OF_ENUM } ) {
383             $spec->{ $VALUES_OF_ENUM }{$_} = undef
384 19         23 for @{ $spec->{values} };
  19         85  
385             }
386 4954         7827 push @other, $spec->{ $VALUES_OF_ENUM };
387             }
388              
389             # Check type
390 10763 100       18290 unless ( $validate{ $type }->( $value, @other ) ) {
391 553   100     4839 $self->_error( ( $value // '[undef]') . " is not of type '$type'" );
392 553         1392 return;
393             }
394              
395             # Check greater than
396 10210 100       44467 if ( exists $spec->{gt} ) {
397 54 100       138 if ( $spec->{gt} >= $value ) {
398 30   50     157 $self->_error( ( $value // '[undef]') . " > $spec->{gt} return false" );
399 30         69 return;
400             }
401             }
402              
403             # Check greater or equal
404 10180 100       16717 if ( exists $spec->{ge} ) {
405 54 100       137 if ( $spec->{ge} > $value ) {
406 10   50     54 $self->_error( ( $value // '[undef]') . " >= $spec->{ge} returns false" );
407 10         23 return;
408             }
409             }
410              
411             # Check less than
412 10170 100       16326 if ( exists $spec->{lt} ) {
413 68 100       169 if ( $spec->{lt} <= $value ) {
414 44   50     205 $self->_error( ( $value // '[undef]') . " < $spec->{lt} returns false" );
415 44         96 return;
416             }
417             }
418              
419             # Check less or equal
420 10126 100       16139 if ( exists $spec->{le} ) {
421 62 100       148 if ( $spec->{le} < $value ) {
422 24   50     120 $self->_error( ( $value // '[undef]') . " <= $spec->{le} returns false" );
423 24         53 return;
424             }
425             }
426              
427             # Check min length
428 10102 100       17212 if ( exists $spec->{min_length} ) {
429 16 100 50     55 if ( $spec->{min_length} > length( $value // '' ) ) {
430 8   50     43 $self->_error( 'length(' . ( $value // '[undef]') . " > $spec->{min_length} returns false" );
431 8         18 return;
432             }
433             }
434              
435             # Check max length
436 10094 100       16179 if ( exists $spec->{max_length} ) {
437 16 100 50     52 if ( $spec->{max_length} < length( $value // '' ) ) {
438 8   50     42 $self->_error( 'length(' . ( $value // '[undef]') . " < $spec->{max_length} returns false" );
439 8         22 return;
440             }
441             }
442              
443             # Check of
444 10086 100       16562 if ( exists $spec->{of} ) {
445 620         817 my @values;
446 620 100       1222 if ( $type eq 'array' ) {
    50          
447 78         170 @values = @$value;
448             }
449             elsif ( $type eq 'hash' ) {
450 542         1178 @values = values %$value;
451             }
452             else {
453 0         0 $self->_error( "Cannot set elements types for $type" );
454 0         0 return;
455             }
456              
457 620 100       1124 if ( !@values ) {
458 18 100 66     56 if ( !exists $spec->{empty} || !$spec->{empty} ) {
459 12         59 $self->_error( ucfirst( $type ) . " cannot be empty" );
460 12         33 return;
461             }
462             }
463              
464 608         1015 for my $v ( @values ) {
465             return
466 918 100       1558 unless $self->validate_value( $v, $spec->{of} );
467             }
468             }
469              
470             # Check code
471 10064 100       16801 if ( exists $spec->{callback} ) {
472 8 100       22 if ( !$spec->{callback}->( $value, @other ) ) {
473 4         24 $self->_error("Callback returned false");
474 4         10 return;
475             }
476             }
477 10060         17344 return 1;
478             }
479              
480              
481              
482             # Primitives
483             # ==========
484             sub number {
485 3411     3411 1 14435 return looks_like_number( $_[1] );
486             }
487              
488             sub positive {
489 73   100 73 1 1268 return $_[0]->number( $_[1] ) && $_[1] > 0;
490             }
491              
492             sub non_negative {
493 79   100 79 1 1304 return $_[0]->number( $_[1] ) && $_[1] >= 0;
494             }
495              
496             sub negative {
497 79   100 79 1 1279 return $_[0]->number( $_[1] ) && $_[1] < 0;
498             }
499              
500             sub non_positive {
501 79   100 79 1 1269 return $_[0]->number( $_[1] ) && $_[1] <= 0;
502             }
503              
504             sub integer {
505 273     273 1 2791 return is_int( $_[1] );
506             }
507              
508             sub positive_int {
509 219     219 1 2631 return is_count( $_[1] );
510             }
511              
512             sub non_negative_int {
513 85     85 1 2298 return is_whole( $_[1] );
514             }
515              
516             sub negative_int {
517 97   100 97 1 2312 return $_[0]->integer( $_[1] ) && $_[1] < 0;
518             }
519              
520             sub non_positive_int {
521 97   100 97 1 2303 return $_[0]->integer( $_[1] ) && $_[1] <= 0;
522             }
523              
524             sub string {
525 5142     5142 1 12469 return is_string( $_[1] );
526             }
527              
528             sub array {
529 116     116 1 3080 return ref $_[1] eq 'ARRAY';
530             }
531              
532             sub hash {
533 7977     7977 1 21004 return ref $_[1] eq 'HASH';
534             }
535              
536             sub enum {
537 4960   100 4960 1 10147 return $_[0]->string( $_[1] ) && exists $_[2]->{$_[1]};
538             }
539              
540             sub code {
541 67     67 1 1780 return ref $_[1] eq 'CODE';
542             }
543              
544             1;
545              
546              
547             __END__