File Coverage

blib/lib/Data/Validate/WithYAML.pm
Criterion Covered Total %
statement 220 220 100.0
branch 118 118 100.0
condition 64 64 100.0
subroutine 30 30 100.0
pod 10 10 100.0
total 442 442 100.0


line stmt bran cond sub pod time code
1             package Data::Validate::WithYAML;
2              
3 34     34   2588632 use strict;
  34         556  
  34         1035  
4 34     34   187 use warnings;
  34         66  
  34         950  
5              
6 34     34   182 use Carp;
  34         71  
  34         2124  
7 33     33   240 use Scalar::Util qw(looks_like_number);
  33         102  
  33         1664  
8 33     33   20627 use YAML::Tiny;
  33         198655  
  33         87918  
9              
10             # ABSTRACT: Validation framework that can be configured with YAML files
11              
12             our $VERSION = '0.20';
13             our $errstr = '';
14              
15              
16             sub new{
17 31     31 1 6161 my ($class,$filename,%args) = @_;
18            
19 31         101 my $self = {};
20 31         99 bless $self,$class;
21            
22 31         188 $self->{__optional__} = {};
23 31         110 $self->{__required__} = {};
24            
25 31         244 $self->_allow_subs( $args{allow_subs} );
26 31         187 $self->_no_steps( $args{no_steps} );
27 31 100       190 $self->_yaml_config( $filename ) or return undef;
28            
29 25         125 return $self;
30             }
31              
32             sub _optional {
33 183     183   894 my ($self) = @_;
34 183         581 $self->{__optional__};
35             }
36              
37             sub _required {
38 483     483   860 my ($self) = @_;
39 483         1786 $self->{__required__};
40             }
41              
42             sub _no_steps {
43 56     56   189 my ($self, $no_steps) = @_;
44 56 100       214 $self->{__no_steps__} = $no_steps if @_ == 2;;
45 56         172 $self->{__no_steps__};
46             }
47              
48              
49             sub set_optional {
50 3     3 1 1431 my ($self,$field) = @_;
51            
52 3         9 my $value = delete $self->_required->{$field};
53 3 100       160 if( $value ) {
54 1         3 $self->_optional->{$field} = $value;
55             }
56             }
57              
58              
59             sub set_required {
60 2     2 1 1345 my ($self,$field) = @_;
61            
62 2         6 my $value = delete $self->_optional->{$field};
63 2 100       8 if( $value ) {
64 1         3 $self->_required->{$field} = $value;
65             }
66             }
67              
68              
69             sub validate{
70 35     35 1 17458 my $self = shift;
71              
72 35         68 my ($part, %hash);
73              
74 35 100 100     238 if ( @_ && @_ % 2 == 0 ) {
75 3         12 %hash = @_;
76 3         6 $part = '';
77             }
78             else {
79 32         110 ($part, %hash) = @_;
80             }
81              
82 35         119 my @fieldnames = $self->fieldnames( $part );
83            
84 35         80 my %errors;
85             my %fields;
86 35         83 my $optional = $self->_optional;
87 35         78 my $required = $self->_required;
88            
89 35         78 for my $name ( @fieldnames ) {
90 111 100       308 $fields{$name} = $optional->{$name} if exists $optional->{$name};
91 111 100       310 $fields{$name} = $required->{$name} if exists $required->{$name};
92              
93 111 100       288 next if $fields{$name}->{no_validate};
94            
95 106         199 my $value = $hash{$name};
96            
97 106         190 my $depends_on = $fields{$name}->{depends_on};
98 106 100       195 if ( $depends_on ) {
99 26 100 100     121 if ( !exists $hash{$depends_on} && !$fields{$name}->{depends_lax} ) {
    100          
100 3         8 $errors{$name} = $self->message( $name );
101 3         7 next;
102             }
103             elsif ( defined $hash{$depends_on} ) {
104            
105 22         45 my $depends_on_value = $hash{$depends_on};
106 22   100     60 my $cases = $fields{$name}->{case} || {};
107            
108 22 100       67 $fields{$name} = $cases->{$depends_on_value} if $cases->{$depends_on_value};
109             }
110             }
111              
112 103   100     279 $fields{$name}->{type} ||= 'optional';
113 103         316 my $success = $self->check( $name, $hash{$name}, $fields{$name} );
114 103 100       252 if ( !$success ) {
115 25         82 $errors{$name} = $self->message( $name );
116             }
117             }
118            
119 35         193 return %errors;
120             }
121              
122              
123             sub fieldnames{
124 41     41 1 2910 my $self = shift;
125              
126 41         76 my ($step, %options);
127              
128 41 100 100     164 if ( @_ && @_ % 2 == 0 ) {
129 1         3 %options = @_;
130 1         2 $step = '';
131             }
132             else {
133 40         94 ($step, %options) = @_;
134             }
135              
136 41         68 my @names;
137 41 100       142 if( defined $step ){
138 38 100       62 @names = @{ $self->{fieldnames}->{$step} || [] };
  38         181  
139             }
140             else{
141 3         7 for my $step ( keys %{ $self->{fieldnames} } ){
  3         16  
142 6         22 push @names, @{ $self->{fieldnames}->{$step} };
  6         28  
143             }
144             }
145            
146 41 100       139 if ( $options{exclude} ) {
147 2         4 my %hash;
148 2         9 @hash{@names} = (1) x @names;
149            
150 2         4 delete @hash{ @{$options{exclude}} };
  2         6  
151            
152 2         9 @names = keys %hash;
153             }
154              
155 41         161 return @names;
156             }
157              
158              
159             sub errstr{
160 3     3 1 3147 my ($self) = @_;
161 3         22 return $errstr;
162             }
163              
164              
165             sub message {
166 30     30 1 808 my ($self,$field) = @_;
167            
168 30   100     72 my $subhash = $self->_required->{$field} || $self->_optional->{$field};
169 30         54 my $message = "";
170            
171 30 100       81 if ( $subhash->{message} ) {
172 24         46 $message = $subhash->{message};
173             }
174              
175 30         113 $message;
176             }
177              
178              
179             sub check_list {
180 6     6 1 1760 my ($self,$field,$values) = @_;
181            
182 6 100       21 return if !$values;
183 5 100       21 return if ref $values ne 'ARRAY';
184            
185 4         6 my @results;
186 4         10 for my $value ( @{$values} ) {
  4         9  
187 3 100       11 push @results, $self->check( $field, $value ) ? 1 : 0;
188             }
189            
190 3         10 return \@results;
191             }
192              
193              
194             sub check{
195 256     256 1 58997 my ($self,$field,$value,$definition) = @_;
196            
197 256         1570 my %dispatch = (
198             min => \&_min,
199             max => \&_max,
200             regex => \&_regex,
201             not_regex => \&_not_regex,
202             length => \&_length,
203             enum => \&_enum,
204             sub => \&_sub,
205             datatype => \&_datatype,
206             );
207            
208 256   100     869 my $subhash = $definition || $self->_required->{$field} || $self->_optional->{$field};
209              
210 256 100 100     1456 if(
    100 100        
      100        
      100        
      100        
      100        
211             ( $definition and $definition->{type} eq 'required' )
212             or ( !$definition and exists $self->_required->{$field} )
213             ){
214 216 100 100     946 return 0 unless defined $value and length $value;
215             }
216             elsif(
217             ( ( $definition and $definition->{type} eq 'optional' )
218             or ( !$definition and exists $self->_optional->{$field} ) )
219             and (not defined $value or not length $value) ){
220 18         97 return 1;
221             }
222            
223 218         387 my $bool = 1;
224            
225 218         714 for my $key( keys %$subhash ){
226 538 100       1484 if( exists $dispatch{$key} ){
    100          
227 293 100       748 unless($dispatch{$key}->($value,$subhash->{$key},$self)){
228 81         139 $bool = 0;
229 81         150 last;
230             }
231             }
232             elsif( $key eq 'plugin' ){
233 6 100       30 my $plugins = ref $subhash->{$key} ? $subhash->{$key} : [$subhash->{$key}];
234              
235 6         13 my $one_not_ok = 0;
236              
237 6         23 for my $plugin ( @{ $plugins } ) {
  6         24  
238 8         61 my ($name) = $plugin =~ m{([A-z0-9_:]+)};
239 8         54 my $module = 'Data::Validate::WithYAML::Plugin::' . $name;
240              
241 8     5   683 eval "use $module";
  5         1874  
  4         543  
  4         108  
242              
243 8 100 100     109 if( not $@ and $module->can('check') ){
244 5         20 my $local_bool = $module->check($value, $subhash);
245 5 100       51 $one_not_ok = 1 if !$local_bool;
246             }
247             else{
248 3         438 croak "Can't check with $module";
249             }
250             }
251              
252 3 100       13 $bool = $one_not_ok ? 0 : 1;
253             }
254             }
255            
256 212         851 return $bool;
257             }
258              
259              
260             sub fieldinfo {
261 3     3 1 34 my ($self, $field) = @_;
262              
263 3   100     8 my $info = $self->_required->{$field} || $self->_optional->{$field};
264 3 100       12 return if !$info;
265              
266 2         12 return $info;
267             }
268              
269             # read config file and parse required and optional fields
270             sub _yaml_config{
271 31     31   97 my ($self,$file) = @_;
272            
273 31 100 100     916 if ( ref $file and 'SCALAR' eq ref $file ) {
    100 100        
    100          
274             eval {
275 5         14 $self->{config} = YAML::Tiny->read_string( ${$file} );
  5         30  
276 4         4491 1;
277 5 100       22 } or do {
278 1         513 $errstr = $@;
279 1         7 return;
280             };
281             }
282             elsif(defined $file and -e $file){
283             eval {
284 22         167 $self->{config} = YAML::Tiny->read( $file );
285 21         290012 1;
286 22 100       83 } or do {
287 1         13398 $errstr = $@;
288 1         8 return;
289             };
290             }
291             elsif(defined $file){
292 3         12 $errstr = 'file does not exist';
293 3         34 return;
294             }
295             else {
296 1         3 $errstr = 'Need path to YAML file';
297 1         6 return;
298             }
299              
300 25 100       555 if ( $self->_no_steps ) {
301 2         12 $self->_add_fields( $self->{config}->[0], '' );
302             }
303             else {
304 23         60 for my $section(keys %{$self->{config}->[0]}){
  23         148  
305 33         106 my $sec_hash = $self->{config}->[0]->{$section};
306 33         114 $self->_add_fields( $sec_hash, $section );
307             }
308             }
309              
310 25         132 return $self->{config};
311             }
312              
313             sub _add_fields {
314 35     35   119 my ($self, $data, $section) = @_;
315              
316 35         144 for my $field( keys %$data ){
317 117 100 100     616 if(exists $data->{$field}->{type} and
    100          
318             $data->{$field}->{type} eq 'required'){
319 52         193 $self->_required->{$field} = $data->{$field};
320              
321 52 100       134 if( exists $self->_optional->{$field} ){
322 4         31 delete $self->_optional->{$field};
323             }
324             }
325             elsif( not exists $self->_required->{$field} ){
326 61         151 $self->_optional->{$field} = $data->{$field};
327             }
328              
329 117         197 push @{$self->{fieldnames}->{$section}}, $field;
  117         480  
330             }
331             }
332              
333             sub _min{
334 83     83   255 my ($value,$min) = @_;
335 83 100       216 return if !looks_like_number $value;
336 81         246 return $value >= $min;
337             }
338              
339             sub _max{
340 123     123   431 my ($value,$max) = @_;
341 123 100       440 return if !looks_like_number $value;
342 120         401 return $value <= $max;
343             }
344              
345             sub _regex{
346 28     28   72 my ($value,$regex_list) = @_;
347              
348 28 100       77 my $regexes = ref $regex_list ? $regex_list : [$regex_list];
349              
350 28         49 for my $regex ( @{ $regexes } ) {
  28         54  
351 30         830 my $re = qr/$regex/;
352 30 100       309 return 0 if $value !~ $re;
353             }
354              
355 13         59 return 1;
356             }
357              
358             sub _not_regex{
359 8     8   18 my ($value,$regex_list) = @_;
360              
361 8 100       21 my $regexes = ref $regex_list ? $regex_list : [$regex_list];
362              
363 8         13 for my $regex ( @{ $regexes } ) {
  8         17  
364 10         96 my $re = qr/$regex/;
365 10 100       79 return if $value =~ $re;
366             }
367              
368 3         10 return 1;
369             }
370              
371             sub _length{
372 27     27   172 my ($value,$check) = @_;
373            
374 27 100       118 if($check =~ /,/){
375 26         197 my ($min,$max) = $check =~ /\s*(\d+)?\s*,\s*(\d+)?/;
376 26         68 my $bool = 1;
377 26 100 100     124 if(defined $min and length $value < $min){
378 7         18 $bool = 0;
379             }
380 26 100 100     77 if(defined $max and length $value > $max){
381 1         3 $bool = 0;
382             }
383 26         95 return $bool;
384             }
385             else{
386 1         9 return length $value == $check;
387             }
388             }
389              
390             sub _enum{
391 23     23   53 my ($value,$list) = @_;
392 23         52 return grep{ $_ eq $value }@$list;
  67         191  
393             }
394              
395             sub _sub {
396 4     4   11 my ($value,$sub,$self) = @_;
397 4         10 $_ = $value;
398            
399 4 100       25 croak "Can't use user defined sub unless it is allowed" if !$self->_allow_subs;
400            
401 2         182 return eval "$sub";
402             }
403              
404             sub _allow_subs {
405 35     35   150 my ($self,$value) = @_;
406            
407 35 100       193 $self->{__allow_subs} = $value if @_ == 2;
408 35         301 $self->{__allow_subs};
409             }
410              
411             sub _datatype {
412 33     33   165 my ($value, $type) = @_;
413              
414 33         74 $type = lc $type;
415              
416 33 100       99 if ( $type eq 'int' ) {
    100          
    100          
417 16 100       64 return if !looks_like_number $value;
418 14 100       95 return if $value !~ m{\A [+-]? \d+ (?:[eE]\d+)? \z}xms;
419 12         119 return 1;
420             }
421             elsif ( $type eq 'num' ) {
422 6 100       34 return if !looks_like_number $value;
423 4         15 return 1;
424             }
425             elsif ( $type eq 'positive_int' ) {
426 10 100       42 return if !looks_like_number $value;
427 8   100     33 return _datatype( $value, 'int' ) && $value > 0;
428             }
429              
430 1         12 croak "Unknown datatype $type";
431             }
432              
433             1;
434              
435             __END__