File Coverage

blib/lib/Config/Validate.pm
Criterion Covered Total %
statement 268 272 98.5
branch 124 126 98.4
condition 27 30 90.0
subroutine 42 44 95.4
pod 5 5 100.0
total 466 477 97.6


line stmt bran cond sub pod time code
1             package Config::Validate;
2              
3 20     20   1408822 use strict;
  20         52  
  20         851  
4 20     20   112 use warnings;
  20         41  
  20         601  
5 20     20   544 use 5.008005;
  20         290  
  20         937  
6              
7             # There is too much DWIMery here for this to be practical
8             ## no critic (RequireArgUnpacking, ProhibitDoubleSigils)
9              
10             {
11 20     20   29939 use Object::InsideOut;
  20         1506870  
  20         150  
12              
13 20     20   14905 use Data::Dumper;
  20         130072  
  20         1835  
14 20     20   20272 use Clone::PP qw(clone);
  20         38142  
  20         138  
15 20     20   1638 use Scalar::Util qw(blessed);
  20         44  
  20         1152  
16 20     20   21789 use Params::Validate qw(:types validate_with);
  20         323530  
  20         8250  
17 20     20   28376 use Carp::Clan;
  20         56711  
  20         200  
18 20     20   54818 use List::MoreUtils qw(any);
  20         42165  
  20         2450  
19              
20 20     20   1535 use Exporter qw(import);
  20         39  
  20         15192  
21             our @EXPORT_OK = qw(validate mkpath);
22             our %EXPORT_TAGS = ('all' => \@EXPORT_OK);
23            
24             our $VERSION = '0.2.6';
25              
26             my @schema :Field
27             :Accessor(schema)
28             :Arg(schema);
29             my @array_allows_scalar :Field
30             :Accessor(array_allows_scalar)
31             :Arg(array_allows_scalar)
32             :Default(1);
33             my @debug :Field
34             :Accessor(debug)
35             :Arg(debug);
36             my @on_debug :Field
37             :Accessor(on_debug)
38             :Arg(on_debug)
39             :Default(\&_debug_print);
40             my @data_path :Field
41             :Accessor(data_path)
42             :Arg(data_path)
43             :Default(0);
44             my @data_path_options :Field
45             :Accessor(data_path_options)
46             :Arg(data_path_options)
47             :Default( {} );
48              
49             my @types :Field;
50              
51             ## no critic(ProhibitSubroutinePrototypes)
52             sub _throw (@);
53             ## use critic
54              
55             my %default_types = (
56             integer => { validate => \&_validate_integer },
57             float => { validate => \&_validate_float },
58             string => { validate => \&_validate_string },
59             boolean => { validate => \&_validate_boolean },
60             hash => { validate => \&_validate_hash },
61             array => { validate => \&_validate_array,
62             byreference => 1,
63             },
64             directory => { validate => \&_validate_directory },
65             file => { validate => \&_validate_file },
66             domain => { validate => \&_validate_domain },
67             hostname => { validate => \&_validate_hostname },
68             nested => { validate => sub { _throw "'nested' is not valid here"; }},
69             );
70              
71             my %types = %default_types;
72              
73             my $have_data_path;
74              
75             sub _init :Init {
76 93     0   177394 my ($self, $args) = @_;
77            
78 93         735 $types[$$self] = clone(\%types);
79              
80 93 100       75315 unless (defined $have_data_path) {
81 17         49 eval { require Data::Path; };
  17         8373  
82 17 50       17592 $have_data_path = $@ eq '' ? 1 : 0;
83             }
84              
85 93 100 66     33162 if ($self->data_path and not $have_data_path) {
86 1         17 _throw "Data::Path requested, but cannot find module";
87             }
88              
89 92         1179 return;
90 20     20   142 }
  20         54  
  20         268  
91              
92             sub _parse_add_type_params {
93             # TODO: This should be updated to allow 'byreference'
94 18     18   159 my $spec = { name => { type => SCALAR },
95             validate => { type => CODEREF,
96             optional => 1,
97             },
98             init => { type => CODEREF,
99             optional => 1,
100             },
101             finish => { type => CODEREF,
102             optional => 1,
103             },
104             };
105             return validate_with(params => \@_,
106             spec => $spec,
107             stack_skip => 2,
108             normalize_keys => sub {
109 102     102   1302 return lc $_[0];
110             },
111 18         373 );
112             }
113              
114             sub add_default_type {
115             # this is a function, but if it's called as a method, that's
116             # fine too.
117 10     10 1 4018 my $self;
118 10 100       28 if (@_) {
119 9 100       35 $self = shift if blessed $_[0];
120 9 100       28 shift if $_[0] eq 'Config::Validate';
121             }
122            
123 10         25 my %p = _parse_add_type_params(@_);
124 9 100       106 if ($self) {
125 1         5 $self->add_type(%p);
126             }
127              
128 9 100       27 if (defined $types{$p{name}}) {
129 1         7 _throw "Attempted to add type '$p{name}' that already exists";
130             }
131              
132 8         28 my $type = clone(\%p);
133 8         534 delete $type->{name};
134 8 100       35 if (keys %$type == 0) {
135 1         5 _throw "No callbacks defined for type '$p{name}'";
136             }
137 7         37 $types{$p{name}} = $type;
138            
139              
140 7         21 return;
141             }
142              
143             sub add_type {
144 8     8 1 9933 my $self = shift;
145 8         33 my %p = _parse_add_type_params(@_);
146            
147 7 100       115 if (defined $types[$$self]{$p{name}}) {
148 1         7 _throw "Attempted to add type '$p{name}' that already exists";
149             }
150            
151 6         28 my $type = clone(\%p);
152 6         745 delete $type->{name};
153 6 100       29 if (keys %$type == 0) {
154 1         8 _throw "No callbacks defined for type '$p{name}'";
155             }
156 5         17 $types[$$self]{$p{name}} = $type;
157 5         19 return;
158             }
159              
160             sub reset_default_types {
161 9     9 1 11679 %types = %default_types;
162 9         33 return;
163             }
164              
165             sub _type_callback {
166 186     186   471 my ($self, $callback, @args) = @_;
167              
168 186         275 while (my ($name, $value) = each %{ $types[$$self] }) {
  2253         15703  
169 2067 100       5339 if (defined $value->{$callback}) {
170 5         19 $value->{$callback}(@args);
171             }
172             }
173 186         377 return;
174             }
175              
176             # Unfortunately, the validate function/method used to not use
177             # Params::Validate, and used to instead be callable as a one
178             # argument version as an instance method, or a two argument version
179             # (schema and config) as a function. This functin is to detect
180             # which way it is being called, and normalize the argument list.
181             sub _parse_validate_args {
182 114     114   408 my (@args) = @_;
183              
184 114 100       387 if (@args < 2) {
185 2         6 _throw "Config::Validate::validate requires at least two arguments";
186             }
187              
188 112         173 my $self;
189 112 100       547 if (blessed $args[0]) {
190             # called as a method
191 67         111 $self = shift @args;
192 67 100       171 if (@args == 1) {
193 65         234 @args = (schema => $schema[$$self],
194             config => $args[0]);
195             } else {
196 2         55 push(@args, schema => $self->schema);
197             }
198             } else {
199 45         257 $self = Config::Validate->new();
200 45 100       1078 if (@args == 2) {
201 44         168 @args = (config => $args[0],
202             schema => $args[1]);
203             }
204             }
205              
206 112         596 my $spec = { schema => { type => HASHREF },
207             config => { type => HASHREF },
208             };
209             my %args = validate_with(params => \@args,
210             spec => $spec,
211             stack_skip => 2,
212             normalize_keys => sub {
213 448     448   3397 return lc $_[0];
214             },
215 112         2489 );
216            
217 112         1549 return ($self, %args);
218             }
219              
220             sub validate {
221 114     114 1 109092 my ($self, %args) = _parse_validate_args(@_);
222 112         521 my ($config, $schema) = (clone($args{config}),
223             clone($args{schema}));
224              
225             # Not sure if Config::General object will be extended or not, so
226             # assume anything in Config::General namespace as a getall method.
227 112         15513 my $config_type = ref $config;
228 112 100       357 if ($config_type =~ /^Config::General/ix) {
229 1         5 $config = { $config->getall() };
230             }
231              
232 112         414 $self->_type_callback('init', $self, $schema, $config);
233 112         424 $self->_validate($config, $schema, []);
234 74         216 $self->_type_callback('finish', $self, $schema, $config);
235              
236 74 50       2481 if ($self->data_path) {
237 0         0 return Data::Path->new($config, $self->data_path_options);
238             }
239 74         3309 return $config;
240             }
241              
242             sub _validate {
243 115     115   209 my ($self, $cfg, $schema, $path) = @_;
244              
245 115         311 $schema = clone($schema);
246 115         7310 my $orig = clone($cfg);
247              
248 115         3977 while (my ($canonical_name, $def) = each %$schema) {
249 118         312 my @curpath = (@$path, $canonical_name);
250 118         383 my @names = _get_aliases($canonical_name, $def, @curpath);
251 117         409 $self->_check_definition_type($def, @curpath);
252              
253 115         166 my $found = 0;
254 115         240 foreach my $name (@names) {
255 120 100       414 next unless defined $cfg->{$name};
256            
257 109 100       275 if ($name ne $canonical_name) {
258 3         6 $cfg->{$canonical_name} = $cfg->{$name};
259 3         4 delete $cfg->{$name};
260 3         5 delete $orig->{$name};
261             }
262            
263 109         524 $self->_debug("Validating ", mkpath(@curpath));
264 109 100       2263 if (lc($def->{type}) eq 'nested') {
265 1         8 $self->_validate($cfg->{$canonical_name}, $schema->{$name}{child}, \@curpath);
266             } else {
267 108         1384 $self->_invoke_validate_callback($cfg, $canonical_name, $def, \@curpath);
268             }
269            
270 79 100       251 if (defined $def->{callback}) {
271 2 100       16 if (ref $def->{callback} ne 'CODE') {
272 1         3 _throw sprintf("%s: callback specified is not a code reference",
273             mkpath(@curpath));
274             }
275 1         14 $def->{callback}($self, $cfg->{$canonical_name}, $def, \@curpath);
276             }
277 78         30208 $found++;
278             }
279            
280 84 100 100     297 if (not $found and defined $def->{default}) {
281 2         5 $cfg->{$canonical_name} = $def->{default};
282 2         5 $found++;
283             }
284            
285 84         168 delete $orig->{$canonical_name};
286              
287 84 100 66     616 if (not $found and (not defined $def->{optional} or not $def->{optional})) {
      66        
288 3         9 _throw "Required item " . mkpath(@curpath) . " was not found";
289             }
290             }
291              
292 78         273 my @unknown = sort keys %$orig;
293 78 100       217 if (@unknown != 0) {
294 1         3 _throw sprintf("%s: the following unknown items were found: %s",
295             mkpath($path), join(', ', @unknown));
296             }
297              
298 77         285 return;
299             }
300              
301             sub _invoke_validate_callback {
302 108     108   294 my ($self, $cfg, $canonical_name, $def, $curpath) = @_;
303              
304 108         620 my $typeinfo = $types[$$self]{$def->{type}};
305 108         199 my $callback = $typeinfo->{validate};
306              
307 108 100       281 if (not defined $callback) {
308 1         6 _throw("No callback defined for type '$def->{type}'");
309             }
310            
311 107 100       264 if ($typeinfo->{byreference}) {
312 7         27 $callback->($self, \$cfg->{$canonical_name}, $def, $curpath);
313             } else {
314 100         287 $callback->($self, $cfg->{$canonical_name}, $def, $curpath);
315             }
316            
317 78         241 return;
318             }
319            
320             sub _get_aliases {
321 118     118   253 my ($canonical_name, $definition, @curpath) = @_;
322            
323 118         260 my @names = ($canonical_name);
324 118 100       359 if (defined $definition->{alias}) {
325 4 100       18 if (ref $definition->{alias} eq 'ARRAY') {
    100          
326 2         3 push(@names, @{$definition->{alias}});
  2         5  
327             } elsif (ref $definition->{alias} eq '') {
328 1         3 push(@names, $definition->{alias});
329             } else {
330 1         3 _throw sprintf("Alias defined for %s is type %s, but must be " .
331             "either an array reference, or scalar",
332             mkpath(@curpath), ref $definition->{alias},
333             );
334             }
335             }
336 117         339 return @names;
337             }
338              
339             sub _check_definition_type {
340 117     117   237 my ($self, $definition, @curpath) = @_;
341 117 100       350 if (not defined $definition->{type}) {
342 1         8 _throw "No type specified for " . mkpath(@curpath);
343             }
344              
345 116 100       427 if (not defined $types[$$self]{$definition->{type}}) {
346 1         8 _throw "Invalid type '$definition->{type}' specified for ",
347             mkpath(@curpath);
348             }
349              
350 115         204 return;
351             }
352              
353             # TODO: Make this callable as a method or function
354             sub mkpath {
355 158 100   158 1 417 @_ = @{$_[0]} if ref $_[0] eq 'ARRAY';
  26         76  
356            
357 158         1008 return '[/' . join('/', @_) . ']';
358             }
359              
360             sub _validate_hash {
361 5     5   10 my ($self, $value, $def, $path) = @_;
362            
363 5 100       16 if (not defined $def->{keytype}) {
364 1         4 _throw "No keytype specified for " . mkpath(@$path);
365             }
366            
367 4 100       16 if (not defined $types[$$self]{$def->{keytype}}) {
368 1         5 _throw "Invalid keytype '$def->{keytype}' specified for " . mkpath(@$path);
369             }
370              
371 3 100       16 if (ref $value ne 'HASH') {
372 1         5 _throw sprintf("%s: should be a 'HASH', but instead is '%s'",
373             mkpath($path), ref $value);
374             }
375              
376 2         10 while (my ($k, $v) = each %$value) {
377 4         11 my @curpath = (@$path, $k);
378 4         10 $self->_debug("Validating ", mkpath(@curpath));
379 4         13 my $callback = $types[$$self]{$def->{keytype}}{validate};
380 4         15 $callback->($self, $k, $def, \@curpath);
381 4 100       18 if ($def->{child}) {
382 2         12 $self->_validate($v, $def->{child}, \@curpath);
383             }
384             }
385 2         3 return;
386             }
387              
388             sub _validate_array {
389 7     7   10 my ($self, $value, $def, $path) = @_;
390            
391 7 100       25 if (not defined $def->{subtype}) {
392 1         4 _throw "No subtype specified for " . mkpath(@$path);
393             }
394              
395 6 100       35 if (not defined $types[$$self]{$def->{subtype}}) {
396 1         6 _throw "Invalid subtype '$def->{subtype}' specified for " . mkpath(@$path);
397             }
398            
399 5 100 100     62 if (ref $value eq 'SCALAR' and $array_allows_scalar[$$self]) {
    100 100        
400 1         4 $$value = [ $$value ];
401 1         2 $value = $$value;
402             } elsif (ref $value eq 'REF' and ref $$value eq 'ARRAY') {
403 2         5 $value = $$value;
404             }
405              
406 5 100       18 if (ref $value ne 'ARRAY') {
407 2         6 _throw sprintf("%s: should be an 'ARRAY', but instead is a '%s'",
408             mkpath($path), ref $value);
409             }
410              
411 3         5 my $index = 0;
412 3         6 foreach my $item (@$value) {
413 8         24 my @path = ( @$path, "[$index]" );
414 8         19 $self->_debug("Validating ", mkpath(@path));
415 8         2141 my $callback = $types[$$self]{$def->{subtype}}{validate};
416 8         22 $callback->($self, $item, $def, \@path);
417 8         23 $index++;
418             }
419 3         8 return;
420             }
421              
422             sub _validate_integer {
423 13     13   26 my ($self, $value, $def, $path) = @_;
424 13 100       75 if ($value !~ /^ -? \d+ $/xo) {
425 1         3 _throw sprintf("%s should be an integer, but has value of '%s' instead",
426             mkpath($path), $value);
427             }
428 12 100 100     62 if (defined $def->{max} and $value > $def->{max}) {
429 1         4 _throw sprintf("%s: %d is larger than the maximum allowed (%d)",
430             mkpath($path), $value, $def->{max});
431             }
432 11 100 100     46 if (defined $def->{min} and $value < $def->{min}) {
433 1         3 _throw sprintf("%s: %d is smaller than the minimum allowed (%d)",
434             mkpath($path), $value, $def->{max});
435             }
436              
437 10         22 return;
438             }
439              
440             sub _validate_float {
441 6     6   13 my ($self, $value, $def, $path) = @_;
442 6 100       102 if ($value !~ /^ -? \d*\.?\d+ $/xo) {
443 1         3 _throw sprintf("%s should be an float, but has value of '%s' instead",
444             mkpath($path), $value);
445             }
446 5 100 100     39 if (defined $def->{max} and $value > $def->{max}) {
447 1         3 _throw sprintf("%s: %f is larger than the maximum allowed (%f)",
448             mkpath($path), $value, $def->{max});
449             }
450 4 100 100     22 if (defined $def->{min} and $value < $def->{min}) {
451 1         4 _throw sprintf("%s: %f is smaller than the minimum allowed (%f)",
452             mkpath($path), $value, $def->{max});
453             }
454            
455 3         8 return;
456             }
457              
458             sub _validate_string {
459 18     18   29 my ($self, $value, $def, $path) = @_;
460            
461 18 100       55 if (defined $def->{maxlen}) {
462 3 100       12 if (length($value) > $def->{maxlen}) {
463 1         4 _throw sprintf("%s: length of string is %d, but must be less than %d",
464             mkpath($path), length($value), $def->{maxlen});
465             }
466             }
467 17 100       47 if (defined $def->{minlen}) {
468 2 100       5 if (length($value) < $def->{minlen}) {
469 1         3 _throw sprintf("%s: length of string is %d, but must be greater than %d",
470             mkpath($path), length($value), $def->{minlen});
471             }
472             }
473 16 100       43 if (defined $def->{regex}) {
474 4 100       41 if ($value !~ $def->{regex}) {
475 2         5 _throw sprintf("%s: regex (%s) didn't match '%s'", mkpath($path),
476             $def->{regex}, $value);
477             }
478             }
479              
480 14         25 return;
481             }
482              
483             sub _validate_boolean {
484 47     47   99 my ($self, $value, $def, $path) = @_;
485            
486 47         145 my @true = qw(y yes t true on);
487 47         119 my @false = qw(n no f false off);
488 47         132 $value =~ s/\s+//xg;
489 47 100   194   374 $value = 1 if any { lc($value) eq $_ } @true;
  194         343  
490 47 100   215   379 $value = 0 if any { lc($value) eq $_ } @false;
  215         426  
491            
492 47 100       265 if ($value !~ /^ [01] $/x) {
493 6         14 _throw sprintf("%s: invalid value '%s', must be: %s", mkpath($path),
494             $value, join(', ', (0, 1, @true, @false)));
495             }
496              
497 41         136 return;
498             }
499            
500             sub _validate_directory {
501 2     2   4 my ($self, $value, $def, $path) = @_;
502              
503 2 100       53 if (not -d $value) {
504 1         3 _throw sprintf("%s: '%s' is not a directory", mkpath($path), $value)
505             }
506 1         3 return;
507             }
508            
509             sub _validate_file {
510 3     3   6 my ($self, $value, $def, $path) = @_;
511              
512 3 100       108 if (not -f $value) {
513 1         3 _throw sprintf("%s: '%s' is not a file", mkpath($path), $value);
514             }
515 2         5 return;
516             }
517              
518             sub _validate_domain {
519 6     6   8 my ($self, $value, $def, $path) = @_;
520              
521 20     20   148696 use Data::Validate::Domain qw(is_domain);
  20         184793  
  20         3663  
522            
523 6         43 my $rc = is_domain($value, { domain_allow_single_label => 1,
524             domain_private_tld => qr/.*/x,
525             }
526             );
527 6 100       315 if (not $rc) {
528 2         6 _throw sprintf("%s: '%s' is not a valid domain name.",
529             mkpath($path), $value);
530             }
531 4         8 return;
532             }
533            
534             sub _validate_hostname {
535 6     6   11 my ($self, $value, $def, $path) = @_;
536              
537 20     20   234 use Data::Validate::Domain qw(is_hostname);
  20         43  
  20         6315  
538            
539 6         44 my $rc = is_hostname($value, { domain_allow_single_label => 1,
540             domain_private_tld => qr/\. acmedns $/xi,
541             }
542             );
543 6 100       267 if (not $rc) {
544 2         4 _throw sprintf("%s: '%s' is not a valid hostname.",
545             mkpath($path), $value);
546             }
547              
548 4         9 return;
549             }
550              
551             sub _debug {
552 121     121   187 my $self = shift;
553              
554 121 100       394 return unless $debug[$$self];
555 5         15 return $on_debug[$$self]->($self, @_);
556             }
557              
558             sub _debug_print {
559 0     0   0 my $self = shift;
560              
561 0         0 print join('', @_), "\n";
562 0         0 return;
563             }
564              
565             ## no critic
566             sub _throw (@) {
567             # Turn off O::IO exception handler
568 45     45   192 local $SIG{__DIE__};
569 45         243 croak @_;
570             }
571             ## use critic
572              
573             }
574             1;
575              
576             __END__