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   1582 use strict;
  3         19  
  3         81  
4 3     3   14 use warnings;
  3         7  
  3         109  
5              
6             our $VERSION = '0.01';
7              
8 3     3   15 use Carp;
  3         4  
  3         305  
9              
10 3     3   20 use Scalar::Util qw/looks_like_number/;
  3         6  
  3         430  
11 3     3   1484 use Data::Types qw/:all/;
  3         4427  
  3         483  
12              
13 3     3   1818 use Data::Dumper;
  3         20594  
  3         7099  
14              
15             my $VALUES_OF_ENUM = '__ VALUES __ OF __ ENUM __';
16              
17             # Constructor
18             #
19             sub new {
20 489     489 1 22307 my ( $class, $specs ) = @_;
21              
22 489 50 66     2350 if ( defined $specs && ref $specs ne 'HASH' ) {
23 0         0 croak "Speccification must be a hashref";
24             }
25              
26 489         1448 my $self = bless {
27             specs => $specs,
28             _errors => [],
29             }, $class;
30              
31 489 100 66     1916 if ( defined $specs && keys %$specs ) {
32 486         1066 $self->validate_specs( $specs );
33             }
34              
35 489         1091 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   4502 my ( $self, $error ) = @_;
44              
45 2177 100       3969 if ( $error ) {
46 709         966 push @{ $self->{_errors} }, $error;
  709         1666  
47             }
48              
49 2177         3672 return;
50             }
51              
52              
53             # List of validatioon errors
54             #
55             sub errors {
56 486 50   486 1 999 return wantarray ? @{ $_[0]->{_errors} } : $_[0]->{_errors};
  486         1955  
57             }
58              
59             # Deletes all collected errors and returns them
60             #
61             sub delete_errors {
62 1468     1468 0 2328 my ( $self ) = @_;
63 1468         2680 my @errors = $self->_error();
64 1468         2985 $self->{_errors} = [];
65 1468         3426 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 323943 my ( $self, $params, $specs ) = @_;
219              
220             # If $specs is not passed, use the one created in constructor,
221             # and skip specs validation
222 976         1587 my $skip_specs_validation = 1;
223 976 100       2285 if ( !defined $specs ) {
224 490   66     2199 $specs //= $self->{specs};
225 490         700 $skip_specs_validation = 0;
226             }
227              
228             # If specs are not known
229             # do not pass
230 976 100       1785 return if !defined $specs;
231              
232             # Clear list of errors
233 972         2482 $self->delete_errors();
234              
235             # If params are not HASHREF or undef
236             # do not pass
237 972 100       2014 unless ( $self->hash( $params ) ) {
238 2         6 $self->_error("Expected a hashref for params");
239 2         15 return;
240             }
241              
242             # Check parameters
243 970         2525 my $ret = $self->_validate( $params, $specs, $skip_specs_validation );
244              
245 970         3220 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 5755 my ( $self, $specs, $path_to_var ) = @_;
256              
257             # This variable contains path to the variable name
258 1479   100     4345 $path_to_var //= '';
259              
260 1479 100       2638 unless ( $self->hash( $specs ) ) {
261 2         9 $self->_error( "Specs MUST be a hashref" );
262 2         10 return;
263             }
264              
265 1477         4535 while ( my ( $variable, $spec ) = each %$specs ) {
266 4941         9159 my $p2v = "$path_to_var/$variable";
267 4941 100       8368 unless ( $self->hash( $spec ) ) {
268 2         8 $self->_error( "Each spec MUST be a hashref: $p2v" );
269 2         7 return;
270             }
271             my $type = exists $spec->{type}
272             ? $spec->{type}
273 4939 100       10705 : ( $spec->{type} = 'any' );
274              
275             # Known type?
276 4939 100       9025 if ( !exists $specs_of_specs{ $type } ) {
277 1         14 $self->_error( "Unknown type '$type' in specs of $p2v" );
278 1         4 return;
279             }
280              
281             # Validate spec
282 4938         6593 my $spec_of_spec = $specs_of_specs{ $type };
283 4938 100       8405 if ( !$self->_validate( $spec, $spec_of_spec, 'skip_specs_validation' ) ) {
284 3         12 $self->_error( "Bad spec for variable $p2v, should be " . Dumper( $spec_of_spec ) );
285 3         23 return;
286             }
287              
288             # Check spec of 'of'
289 4935 50 66     15702 if ( exists $spec->{of} && !$self->validate_specs( { of => $spec->{of} }, $p2v ) ) {
290 0         0 return;
291             }
292             }
293              
294 1471         4154 return 1;
295             }
296              
297             # Actual validation of parameters
298             #
299             sub _validate {
300 5908     5908   9919 my ( $self, $params, $specs, $skip_specs_validation ) = @_;
301              
302             # Check mandatory params
303             return
304 5908 100       9890 unless $self->required_params( $params, $specs );
305              
306             # Check unknown params
307             return
308 5905 100       10521 unless $self->unknown_params( $params, $specs );
309              
310 5902 50 66     11215 if ( !$skip_specs_validation && !$self->validate_specs( $specs ) ) {
311 0         0 return;
312             }
313              
314 5902         14036 while( my ( $name, $value ) = each %$params ) {
315 9923 50       17452 if ( !exists $specs->{ $name } ) {
316 0         0 $self->_error( "Can't find specs for $name" );
317 0         0 return;
318             }
319 9923         13255 my $spec = $specs->{ $name };
320              
321 9923         15460 my $valid = $self->validate_value( $value, $spec );
322 9923 100       29455 return unless $valid;
323             }
324              
325 5209         9800 return 1;
326             }
327              
328             # Checks whether all required params exist
329             #
330             sub required_params {
331 5908     5908 0 8794 my ( $self, $params, $specs ) = @_;
332              
333 5908         7396 my $ret = 1;
334 5908         15155 for my $par ( keys %$specs ) {
335 41286         52063 my $spec = $specs->{ $par };
336 41286 50 66     78377 if ( exists $spec->{required} && $spec->{required} ) {
337 5490 100       11034 if ( !exists $params->{ $par } ) {
338 3         13 $self->_error( "Required param '$par' does not exist" );
339 3         7 $ret = 0;
340             }
341             }
342             }
343              
344 5908         13235 return $ret;
345             }
346              
347             # Check whether unknown params exist
348             #
349             sub unknown_params {
350 5905     5905 0 8870 my ( $self, $params, $specs ) = @_;
351              
352 5905         7401 my $ret = 1;
353 5905         11092 for my $par ( keys %$params ) {
354 9929 100       17868 if ( !exists $specs->{ $par } ) {
355 3         26 $self->_error("Unknown param '$par'");
356 3         5 $ret = 0;
357             }
358             }
359              
360 5905         11561 return $ret;
361             }
362              
363              
364             # Valdates value against spec
365             #
366             sub validate_value {
367 10841     10841 0 17085 my ( $self, $value, $spec ) = @_;
368              
369 10841   50     19709 my $type = $spec->{type} || 'any';
370 10841   66     19869 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     19223 if ( $undef && !defined $value ) {
375 78         190 return 1;
376             }
377              
378 10763         14417 my @other = ();
379             # Enum
380 10763 100       18522 if ( $type eq 'enum' ) {
381             # Create a hash
382 4954 100       9188 if ( !exists $spec->{ $VALUES_OF_ENUM } ) {
383             $spec->{ $VALUES_OF_ENUM }{$_} = undef
384 19         27 for @{ $spec->{values} };
  19         106  
385             }
386 4954         8008 push @other, $spec->{ $VALUES_OF_ENUM };
387             }
388              
389             # Check type
390 10763 100       19024 unless ( $validate{ $type }->( $value, @other ) ) {
391 553   100     4783 $self->_error( ( $value // '[undef]') . " is not of type '$type'" );
392 553         1378 return;
393             }
394              
395             # Check greater than
396 10210 100       44294 if ( exists $spec->{gt} ) {
397 54 100       134 if ( $spec->{gt} >= $value ) {
398 30   50     182 $self->_error( ( $value // '[undef]') . " > $spec->{gt} return false" );
399 30         71 return;
400             }
401             }
402              
403             # Check greater or equal
404 10180 100       16851 if ( exists $spec->{ge} ) {
405 54 100       140 if ( $spec->{ge} > $value ) {
406 10   50     75 $self->_error( ( $value // '[undef]') . " >= $spec->{ge} returns false" );
407 10         24 return;
408             }
409             }
410              
411             # Check less than
412 10170 100       16201 if ( exists $spec->{lt} ) {
413 68 100       181 if ( $spec->{lt} <= $value ) {
414 44   50     213 $self->_error( ( $value // '[undef]') . " < $spec->{lt} returns false" );
415 44         94 return;
416             }
417             }
418              
419             # Check less or equal
420 10126 100       16179 if ( exists $spec->{le} ) {
421 62 100       145 if ( $spec->{le} < $value ) {
422 24   50     121 $self->_error( ( $value // '[undef]') . " <= $spec->{le} returns false" );
423 24         60 return;
424             }
425             }
426              
427             # Check min length
428 10102 100       16958 if ( exists $spec->{min_length} ) {
429 16 100 50     53 if ( $spec->{min_length} > length( $value // '' ) ) {
430 8   50     37 $self->_error( 'length(' . ( $value // '[undef]') . " > $spec->{min_length} returns false" );
431 8         19 return;
432             }
433             }
434              
435             # Check max length
436 10094 100       16827 if ( exists $spec->{max_length} ) {
437 16 100 50     85 if ( $spec->{max_length} < length( $value // '' ) ) {
438 8   50     38 $self->_error( 'length(' . ( $value // '[undef]') . " < $spec->{max_length} returns false" );
439 8         16 return;
440             }
441             }
442              
443             # Check of
444 10086 100       16193 if ( exists $spec->{of} ) {
445 620         807 my @values;
446 620 100       1313 if ( $type eq 'array' ) {
    50          
447 78         165 @values = @$value;
448             }
449             elsif ( $type eq 'hash' ) {
450 542         1170 @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       1155 if ( !@values ) {
458 18 100 66     60 if ( !exists $spec->{empty} || !$spec->{empty} ) {
459 12         49 $self->_error( ucfirst( $type ) . " cannot be empty" );
460 12         28 return;
461             }
462             }
463              
464 608         1040 for my $v ( @values ) {
465             return
466 918 100       1581 unless $self->validate_value( $v, $spec->{of} );
467             }
468             }
469              
470             # Check code
471 10064 100       16073 if ( exists $spec->{callback} ) {
472 8 100       25 if ( !$spec->{callback}->( $value, @other ) ) {
473 4         22 $self->_error("Callback returned false");
474 4         10 return;
475             }
476             }
477 10060         17366 return 1;
478             }
479              
480              
481              
482             # Primitives
483             # ==========
484             sub number {
485 3411     3411 1 15321 return looks_like_number( $_[1] );
486             }
487              
488             sub positive {
489 73   100 73 1 1520 return $_[0]->number( $_[1] ) && $_[1] > 0;
490             }
491              
492             sub non_negative {
493 79   100 79 1 1574 return $_[0]->number( $_[1] ) && $_[1] >= 0;
494             }
495              
496             sub negative {
497 79   100 79 1 1529 return $_[0]->number( $_[1] ) && $_[1] < 0;
498             }
499              
500             sub non_positive {
501 79   100 79 1 1601 return $_[0]->number( $_[1] ) && $_[1] <= 0;
502             }
503              
504             sub integer {
505 273     273 1 3184 return is_int( $_[1] );
506             }
507              
508             sub positive_int {
509 219     219 1 3087 return is_count( $_[1] );
510             }
511              
512             sub non_negative_int {
513 85     85 1 2783 return is_whole( $_[1] );
514             }
515              
516             sub negative_int {
517 97   100 97 1 2835 return $_[0]->integer( $_[1] ) && $_[1] < 0;
518             }
519              
520             sub non_positive_int {
521 97   100 97 1 2796 return $_[0]->integer( $_[1] ) && $_[1] <= 0;
522             }
523              
524             sub string {
525 5142     5142 1 13240 return is_string( $_[1] );
526             }
527              
528             sub array {
529 116     116 1 3583 return ref $_[1] eq 'ARRAY';
530             }
531              
532             sub hash {
533 7977     7977 1 22697 return ref $_[1] eq 'HASH';
534             }
535              
536             sub enum {
537 4960   100 4960 1 10914 return $_[0]->string( $_[1] ) && exists $_[2]->{$_[1]};
538             }
539              
540             sub code {
541 67     67 1 2103 return ref $_[1] eq 'CODE';
542             }
543              
544             1;
545              
546              
547             __END__