File Coverage

blib/lib/Scalar/Validation.pm
Criterion Covered Total %
statement 269 312 86.2
branch 88 110 80.0
condition 21 33 63.6
subroutine 67 78 85.9
pod 6 25 24.0
total 451 558 80.8


line stmt bran cond sub pod time code
1             # perl
2             #
3             # Class Scalar::Validation
4             #
5             # Simple rule based validation package for scalar values
6             #
7             # Ralf Peine, Thu Sep 4 08:34:45 2014
8             #
9             # More documentation at the end of file
10             #------------------------------------------------------------------------------
11              
12             $VERSION = "0.614";
13              
14             package Scalar::Validation;
15              
16 1     1   26950 use base qw (Exporter);
  1         2  
  1         60  
17              
18 1     1   6 use strict;
  1         2  
  1         25  
19 1     1   5 use warnings;
  1         6  
  1         129  
20              
21             our @EXPORT = qw();
22             our @EXPORT_OK = qw (validate is_valid validate_and_correct npar named_parameter par parameter
23             get_rules rule_known declare_rule delete_rule replace_rule enum Enum enum_explained Enum_explained
24             greater_than greater_equal less_than less_equal equal_to g_t g_e l_t l_e
25             is_a
26             convert_to_named_params parameters_end p_end
27             validation_trouble validation_messages get_and_reset_validation_messages prepare_validation_mode);
28              
29             our %EXPORT_TAGS = (
30             all => [qw(validate is_valid validate_and_correct npar named_parameter par parameter
31             get_rules rule_known declare_rule delete_rule replace_rule enum Enum enum_explained Enum_explained
32             greater_than greater_equal less_than less_equal equal_to g_t g_e l_t l_e
33             is_a
34             convert_to_named_params parameters_end p_end
35             validation_trouble validation_messages get_and_reset_validation_messages prepare_validation_mode)],
36             );
37              
38 1     1   5 use Carp;
  1         2  
  1         8410  
39             # use Data::Dumper;
40              
41             # ------------------------------------------------------------------------------
42             #
43             # Initiliazation
44             #
45             # ------------------------------------------------------------------------------
46              
47             our $ignore_callers = { __PACKAGE__ , 1 };
48              
49             our $ignore_caller_pattern;
50              
51             update_caller_pattern();
52              
53             _init_run_API();
54              
55             sub update_caller_pattern {
56 1     1 0 101 $ignore_caller_pattern = eval ("qr/^(".join("|",keys (%$ignore_callers)).")/o");
57             }
58              
59             sub ignore_caller {
60 0     0 0 0 my $module_string = shift;
61              
62 0 0 0     0 die "not a module string: '$module_string'" unless $module_string || $module_string =~ /^[\w:]+$/;
63            
64 0         0 $ignore_callers->{$module_string} = 1;
65 0         0 update_caller_pattern();
66             }
67              
68             # ------------------------------------------------------------------------------
69             #
70             # default actions, not changable
71             #
72             # ------------------------------------------------------------------------------
73              
74             my $croak_sub = sub { croak "Error: ",@_; };
75              
76             my $get_caller_info_default = sub {
77             my ($module, $file_name, $line, $sub_name);
78             $sub_name = __PACKAGE__;
79             my $call_level = 1;
80              
81             while ($sub_name =~ $ignore_caller_pattern) {
82             ($module, $file_name, $line, $sub_name) = caller($call_level++);
83             $sub_name = '' unless $sub_name;
84             }
85            
86             $sub_name = "MAIN" unless $sub_name;
87             return $sub_name;
88             };
89              
90             # ------------------------------------------------------------------------------
91             #
92             # variables my be overwritten by user
93             #
94             # ------------------------------------------------------------------------------
95              
96             our $message_store = undef; # local $Scalar::Validation::message_store = []; # to start storing messages
97             our $trouble_level = 0; # to count failed validations. Not affected by is_valid(...)
98             our $off = 0; # no validation checks if $off == 1
99             our $validate_defaults = 1; # validate default values defined by -Default rule, if set
100              
101             # ------------------------------------------------------------------------------
102             #
103             # actions, changable
104             #
105             # ------------------------------------------------------------------------------
106              
107             our $fail_action = $croak_sub;
108             our $get_caller_info = $get_caller_info_default;
109              
110             # ------------------------------------------------------------------------------
111             #
112             # private vars of Validation "Instance"
113             #
114             # ------------------------------------------------------------------------------
115             my $non_blessed = {
116             REF => 1,
117             ARRAY => 1,
118             HASH => 1,
119             };
120              
121             my $special_rules;
122             my $rule_store;
123             my $get_content_subs;
124              
125              
126             # ------------------------------------------------------------------------------
127             #
128             # normal rules, can be added, replaced, removed
129             #
130             # ------------------------------------------------------------------------------
131              
132             $rule_store = {
133              
134             # --- This rules are needed for Validation.pm to work, don't delete or change! ---
135            
136             Defined => { -name => 'Defined',
137             -where => sub { defined $_ },
138             -message => sub { "value is not defined" },
139             -owner => 'CPAN',
140             -description => "Value is defined",
141             },
142             Filled => { -name => 'Filled',
143             -where => sub { defined $_ and ref($_) eq '' and $_ ne '' },
144             -message => sub { "value is not set" },
145             -owner => 'CPAN',
146             -description => "Value is Scalar and defined and not empty ('')",
147             },
148             Empty => { -name => 'Empty',
149             -where => sub { !defined $_ or $_ eq '' },
150             -message => sub { "value $_ has to be empty" },
151             -owner => 'CPAN',
152             -description => "Value is not defined or ''",
153             },
154             Optional => { -name => 'Optional',
155             -where => sub { 1; },
156             -message => sub { "value is optional" },
157             -owner => 'CPAN',
158             -description => "Value is optional, cannot fail. Use as last entry in -Or rule.",
159             },
160             String => { -name => 'String',
161             -where => sub { defined $_ and ref($_) eq '' },
162             -message => sub { "value $_ is not a string" },
163             -owner => 'CPAN',
164             -description => "Values is a Scalar and defined",
165             },
166             Int => { -name => 'Int',
167             -as => 'Filled',
168             -where => sub { /^[\+\-]?\d+$/ },
169             -message => sub { "value $_ is not an integer" },
170             -owner => 'CPAN',
171             -description => "Value is an integer",
172             },
173             Even => { -name => 'Even',
174             -as => 'Int',
175             -where => sub { $_ % 2 ? 0: 1; },
176             -message => sub { "value $_ is not an integer or not even"},
177             -owner => 'CPAN',
178             -description => 'Value is an even integer ($_ % 2 == 0)',
179             },
180             Scalar => { -name => 'Scalar',
181             -where => sub { ref($_) eq '' },
182             -message => sub { "value $_ is not a scalar" },
183             -owner => 'CPAN',
184             -description => 'Value is a Scalar : ref ($_) eq ""',
185             },
186             Ref => { -name => 'Ref',
187             -where => sub { $_ and ref($_) ne '' },
188             -message => sub { "value $_ is not a reference" },
189             -owner => 'CPAN',
190             -description => "Value is a reference and not a scalar.",
191             },
192             ArrayRef => { -name => 'ArrayRef',
193             -where => sub { $_ and ref($_) eq 'ARRAY' },
194             -message => sub { "value $_ is not a array reference" },
195             -owner => 'CPAN',
196             -description => "Value is an Array reference.",
197             },
198             HashRef => { -name => 'HashRef',
199             -where => sub { $_ and ref($_) eq 'HASH' },
200             -message => sub { "value $_ is not a hash reference" },
201             -owner => 'CPAN',
202             -description => "Value is a Hash reference.",
203             },
204             CodeRef => { -name => 'CodeRef',
205             -where => sub { $_ and ref($_) eq 'CODE' },
206             -message => sub { "value $_ is not a code reference" },
207             -owner => 'CPAN',
208             -description => "Value is a Code reference.",
209             },
210             Class => { -name => 'Class',
211             -where => sub { return 0 unless $_;
212             my $type_name = ref($_);
213             !$type_name || $non_blessed->{$type_name} ? 0: 1;
214             },
215             -message => sub { "value $_ is not a reference" },
216             -owner => 'CPAN',
217             -description => "Value is a reference and not a scalar.",
218             },
219              
220             # --- Some additional global rules --------------------
221            
222             ExistingFile => { -name => 'ExistingFile',
223             -as => 'Filled',
224             -where => sub { -f $_ },
225             -message => sub { "$_ is not a valid name of an existing file"},
226             -owner => 'CPAN',
227             -description => "File with given file name has to exist"
228             },
229              
230             Bool => { -name => 'Bool',
231             -where => sub { ref ($_) ? 0: 1 },
232             -message => sub { "value $_ is not a bool value" },
233             -owner => 'CPAN',
234             -description => "Value is a Scalar, all values including undef allowed",
235             },
236             PositiveInt => { -name => 'PositiveInt',
237             -as => 'Int',
238             -where => sub { $_ >= 0 },
239             -message => sub { "value $_ is not a positive integer" },
240             -owner => 'CPAN',
241             -description => "Value is a positive integer",
242             },
243             NegativeInt => { -name => 'NegativeInt',
244             -as => 'Int',
245             -where => sub { $_ < 0 },
246             -message => sub { "value $_ is not a negative integer" },
247             -owner => 'CPAN',
248             -description => "Value is a negative Integer",
249             },
250             Float => { -name => 'Float',
251             -as => 'Filled',
252             -where => sub { /^[\+\-]?\d+(\.\d+)?([Ee][\+-]?\d+)?$/ },
253             -message => sub { "value $_ is not a float" },
254             -owner => 'CPAN',
255             -description => "Value is a floating number with optional exponent",
256             },
257             PositiveFloat => { -name => 'PositiveFloat',
258             -as => 'Float',
259             -where => sub { $_ > 0 },
260             -message => sub { "value $_ is not a positive float" },
261             -owner => 'CPAN',
262             -description => "Value is a positive floating number with optional exponent",
263             },
264             NegativeFloat => { -name => 'NegativeFloat',
265             -as => 'Float',
266             -where => sub { $_ < 0 },
267             -message => sub { "value $_ is not a negative float" },
268             -owner => 'CPAN',
269             -description => "Value is a negative floating number with optional exponent",
270             },
271             };
272              
273             # ------------------------------------------------------------------------------
274             #
275             # gets content of ref container
276             #
277             # ------------------------------------------------------------------------------
278              
279             $get_content_subs = {
280             HASH => sub { my @keys = keys %$_; return scalar @keys ? \@keys: undef; },
281             ARRAY => sub { return scalar @$_ ? $_: undef; },
282             };
283              
284             # ------------------------------------------------------------------------------
285             #
286             # special rules (combined and other), not changable
287             #
288             # ------------------------------------------------------------------------------
289              
290             $special_rules = {
291             -Optional => {
292             -value_position => 3,
293             -code => sub {
294             # my $subject_info = shift || '';
295             # my $rule_info = shift;
296             local $_ = $_[2];
297              
298             # skip one param if special rule follows
299             my $special_rule = $special_rules->{$_[1]};
300             if ($special_rule) {
301             $_ = $_[$special_rule->{-value_position}];
302             }
303             ;
304            
305             return $_ if !defined $_; # value not set
306             return validate(@_);
307             }
308             },
309             -Default => {
310             -value_position => 4,
311             -code => sub {
312             my $subject_info = shift || '';
313             my $default = shift;
314             my $rule_info = shift;
315             # local $_ = $_[3];
316              
317             my $value_idx = 0;
318              
319             # --- skip x params if special rule follows ---
320             my $special_rule = $special_rules->{$rule_info};
321             if ($special_rule) {
322             my $special_idx = $special_rule->{-value_position};
323             $value_idx = $special_idx - 2 if $special_idx >= 0;
324             }
325              
326             my $value = $_[$value_idx];
327              
328             # --- value set, validate it ---
329             return validate($subject_info, $rule_info, @_)
330             if (defined $value && $value ne '');
331              
332             # --- value not set ----
333             unless (defined $default) {
334             $trouble_level++;
335             $fail_action->("Rules: Default value for rule -Default not set!");
336             return $_;
337             }
338              
339             if (ref($default) eq 'CODE') {
340             $default = $default->();
341             }
342              
343             return $default unless $validate_defaults;
344              
345             my @args = @_;
346            
347             $args[$value_idx] = $default;
348              
349             # --- default has also to be validated!! ---
350             return validate($subject_info, $rule_info, @args)
351             }
352             },
353             -And => {
354             -value_position => 3,
355             -code => sub {
356             my $subject_info = shift || '';
357             my $rule_list = shift;
358             local $_ = shift;
359             my $message_ref = shift;
360            
361             my $rule_exists = 0;
362             my $orig_value = $_;
363            
364             foreach my $rule (@$rule_list) {
365             if (!defined $rule || $rule eq '') {
366             $trouble_level++;
367             $fail_action->("Rules: rule for validation not set");
368             next # in case of fail action doesn't die
369             }
370            
371             if ($rule) {
372             my $special_rule = $special_rules->{$rule};
373            
374             if ($special_rule) {
375             $trouble_level++;
376             $fail_action->("Rules: cannot call any special rule inside of rule '-And'\n");
377             next;
378             }
379              
380             validate ($subject_info, $rule, $_, $message_ref);
381             $rule_exists = 1;
382             }
383             }
384            
385             $trouble_level++;
386             $fail_action->("Rules: No rule found in list to be validated") unless $rule_exists;
387            
388             return $orig_value;
389             }
390             },
391             -Or => {
392             -value_position => 3,
393             -code => sub {
394             my $subject_info = shift || '';
395             my $rule_list = shift;
396             local $_ = shift;
397             my $message_ref = shift;
398            
399             my $rule_exists = 0;
400             my $orig_value = $_;
401            
402             foreach my $rule_info (@$rule_list) {
403             if (!defined $rule_info || $rule_info eq '') {
404             $trouble_level++;
405             $fail_action->("Rules: rule for validation not set");
406             next # in case of fail action doesn't die
407             }
408            
409             next unless $rule_info;
410            
411             my $rule_ref = $rule_store->{$rule_info};
412             unless ($rule_ref) {
413             my $special_rule = $special_rules->{$rule_info};
414            
415             if ($special_rule) {
416             $trouble_level++;
417             $fail_action->("Rules: cannot call any special rule inside of rule '-Or'\n");
418             next;
419             }
420              
421             my $ref_type = ref ($rule_info);
422            
423             unless ($ref_type) {
424             $trouble_level++;
425             $fail_action->("Rules: unknown rule '$rule_info' for validation");
426             next; # in case of fail action doesn't die
427             } elsif ($ref_type eq 'HASH') { # given rule
428             $rule_ref = $rule_info;
429             # TODO: validate rule ...
430             } elsif ($ref_type eq 'CODE') { # where condition for rule
431             $rule_ref = {
432             -where => $rule_info,
433             -message => sub { "$_ does not match free defined rule" },
434             };
435             } else {
436             $trouble_level++;
437             $fail_action->("Rules: cannot handle ref type '$ref_type' of rule '$rule_info' for validation");
438             next; # in case of fail action doesn't die
439             }
440             }
441              
442             if ($rule_ref) {
443             my $test_message_ref = $message_ref || $rule_ref->{-message};
444              
445             my $parent_is_valid = defined $rule_ref->{-as}
446             ? _check_parent_rules($rule_ref->{-as}, $_)
447             : 1;
448             return $orig_value if $parent_is_valid && $rule_ref->{-where}->();
449              
450             $rule_exists = 1;
451             }
452             }
453            
454             $trouble_level++;
455             $fail_action->("Rules: No rule found in list to be validated") unless $rule_exists;
456              
457             my $result = _do_fail($subject_info, $message_ref || sub { "No rule matched of [".join(', ', @$rule_list)."]";});
458             return $result if defined $result;
459              
460             return $orig_value;
461             }
462             },
463             -Enum => {
464             -value_position => 3,
465             -code => sub {
466             my $subject_info = shift || '';
467             my $enum_ref = shift;
468             local $_ = shift;
469             my $message_ref = shift;
470            
471             my $orig_value = $_;
472              
473             my $arg_type = ref ($enum_ref);
474             if ($arg_type eq 'ARRAY') {
475             $enum_ref = { map {$_=> 1} @$enum_ref };
476             }
477             elsif ($arg_type ne 'HASH') {
478             _do_fail($subject_info, sub {"-Enum needs HASH_ref as second parameter";});
479             }
480              
481             unless (defined $_ && $enum_ref->{$_}) {
482             my $result = _do_fail($subject_info, $message_ref ||
483             sub { "value $_ unknown, allowed values are: [ "
484             .join (", ", sort (keys(%$enum_ref)))." ]"; }
485             );
486             return $result if defined $result;
487              
488             }
489             return $orig_value;
490             }
491             },
492             -Range => {
493             -value_position => 4,
494             -code => sub {
495             my $subject_info = shift || '';
496             my $range_ref = shift;
497             my $rule = shift;
498             local $_ = shift;
499             my $message_ref = shift;
500            
501             my $wrong_call_message_sub_ref
502             = sub { "-Range needs ARRAY_ref containing two values [min max] as second parameter" };
503              
504             my $orig_value = $_;
505            
506             unless (ref($range_ref) eq 'ARRAY') {
507             _do_fail($subject_info, $wrong_call_message_sub_ref);
508             return $orig_value;
509             }
510              
511             unless (scalar @$range_ref == 2) {
512             _do_fail($subject_info, $wrong_call_message_sub_ref);
513             return $orig_value;
514             }
515              
516             my ($min, $max) = @$range_ref;
517             if ($min > $max) {
518             _do_fail($subject_info, sub { "(min) $min > $max (max) in range definition"; });
519             return $orig_value;
520             }
521              
522             # type check by is_valid to return here if fails
523             my @messages;
524             my $is_valid;
525             {
526             local ($message_store) = [];
527             $is_valid = is_valid ($subject_info, $rule, $_, $message_ref);
528             @messages = @{validation_messages()};
529             }
530            
531             unless ($is_valid) {
532             my $message = join ("\n", @messages);
533             push (@$message_store, $message) if $message_store;
534             $trouble_level++;
535             my $result = $fail_action->($message);
536             return $result if defined $result;
537              
538             return $orig_value;
539             }
540            
541             unless ($min <= $_ && $_<= $max) {
542             my $result = _do_fail($subject_info, sub {"value $_ is out of range [$min,$max]"});
543             return $result if defined $result;
544             return $orig_value;
545             }
546              
547             return $orig_value;
548             }
549             },
550             -RefEmpty => {
551             -value_position => 3,
552             -code => sub {
553            
554             my $subject_info = shift || '';
555             local $_ = shift;
556             my $message_ref = shift;
557            
558             my $content_ref = _ref_empty_check($subject_info, $_, $message_ref);
559            
560             return undef unless defined $content_ref;
561            
562             my $count_results = scalar @$content_ref;
563             return 0 unless $count_results;
564            
565             _do_fail($subject_info, sub { "Should be empty, but contains $count_results entries: [ ".
566             join (", ", @$content_ref)." ];" });
567            
568             return $count_results;
569             }
570             },
571             };
572              
573             # ------------------------------------------------------------------------------
574             #
575             # internal Methods
576             #
577             # ------------------------------------------------------------------------------
578              
579             sub _handle_enum_explained {
580 2     2   6 my $transform_key_ref = shift;
581 2         5 my $transformed_text = shift;
582 2         3 my $rule_name = shift;
583 2         4 my @enum_args;
584             my @enums_list;
585 0         0 my %enums;
586            
587 2         6 foreach my $arg (@_) {
588 24 100 66     93 if ($arg eq 1 or $arg eq 0) {
589             # arg is complete
590 6         10 my $last_idx = $#enum_args;
591              
592 6 50       13 if ($last_idx < 1) {
593 0         0 $trouble_level++;
594 0         0 $fail_action->("Rules: not enough configuration values for enum '$enum_args[0]'");
595             }
596            
597 6         13 my $explanation = $enum_args[$last_idx];
598 6 100       16 map { my $key = $transform_key_ref ? $transform_key_ref->($_): $_;
  12         27  
599 12         24 $enums{$key} = $explanation;
600 12         26 push (@enums_list, $key);
601             } @enum_args[0..--$last_idx];
602 6         19 @enum_args = ();
603             }
604             else {
605 18         79 push (@enum_args, $arg);
606             }
607             }
608              
609             my $validation_sub_ref = $transform_key_ref
610 3 50   3   27 ? sub { defined $_ && defined $enums{$transform_key_ref->($_)} }
611 2 50   3   12 : sub { defined $_ && defined $enums{$_} };
  3 100       27  
612            
613             return ($rule_name,
614             -where => $validation_sub_ref,
615             -enum => \%enums,
616 2     2   20 -message => sub { "$rule_name: value $_ unknown, allowed values$transformed_text ".
617             "are: [ ".join (", ", @enums_list)." ]" }
618 2         23 );
619             }
620              
621             sub _check_parent_rules {
622 231     231   302 my $rule_name = shift;
623 231         371 local $_ = shift;
624              
625 231         255 my $orig_value = $_;
626              
627 231         363 my $rule_ref = $rule_store->{$rule_name};
628              
629 231 100       494 unless ($rule_ref) {
630 1         3 $trouble_level++;
631 1         6 $fail_action->("Rules: unknown rule '$rule_name' for validation");
632 0         0 return 0; # in case of fail action doesn't die
633             }
634            
635 230 100       658 if (defined $rule_ref->{-as}) {
636 64 50       156 return 0 unless _check_parent_rules($rule_ref->{-as}, $_);
637             }
638            
639 230         746 return $rule_ref->{-where}->();
640             }
641              
642             sub _ref_empty_check {
643 5   50 5   20 my $subject_info = shift || '';
644 5         10 local $_ = shift;
645 5         10 my $message_ref = shift;
646              
647 5         13 my $ref_type = ref($_);
648              
649 5 100       16 unless ($ref_type) {
650 2     2   10 _do_fail($subject_info, sub { "Not a reference: $_" });
  2         7  
651 0         0 return undef;
652             }
653            
654 3         136 my $get_contents_ref = $get_content_subs->{$ref_type};
655              
656 3 100       17 unless ($get_contents_ref) {
657 1     1   10 _do_fail($subject_info, sub { "could not check, if $ref_type is empty" });
  1         4  
658 0         0 return undef;
659             }
660            
661 2         11 return $get_contents_ref->();
662             }
663              
664             sub _do_fail {
665 25     25   46 my $subject_info = shift;
666 25         369 my $message_ref = shift;
667              
668 25         37 $trouble_level++;
669              
670 25 100       116 $_ = defined ($_) ? "'$_'" : '';
671              
672 25         54 my $message = $get_caller_info->()."($subject_info): ".$message_ref->();
673 25 50       92 push (@$message_store, $message) if $message_store;
674              
675 25         57 return $fail_action->($message);
676             }
677              
678             # ------------------------------------------------------------------------------
679             #
680             # API Methods
681             #
682             # ------------------------------------------------------------------------------
683              
684             sub _init_run_API {
685 1     1   4 *npar = *named_parameter;
686              
687 1         2 *parameter = *_do_validate;
688 1         2 *par = *parameter;
689              
690 1         2 *validate = *_do_validate;
691              
692 1         1 *p_end = *parameters_end;
693              
694 1         2 *g_t = *greater_than;
695 1         2 *g_e = *greater_equal;
696 1         2 *l_t = *less_than;
697 1         1 *l_e = *less_equal;
698              
699             }
700              
701             sub convert_to_named_params {
702 17     17 0 95 my $array_ref = validate (args => ArrayRef => shift);
703              
704             validate (arg_count => Even => scalar @$array_ref =>
705 16     1   69 sub { "Even number of args needed to build a hash, but arg-count = $_" });
  1         5  
706 15         99 return @$array_ref;
707             }
708              
709             sub parameters_end {
710 12     12 0 47 my $container_ref = par (container_ref => -Or => [HashRef => 'ArrayRef'] => shift);
711 12   50     39 my $message_text = par (message_text => Scalar => shift) || "extra parameters found";
712              
713 12         22 my $container_type = ref ($container_ref);
714 12 50       42 if ($container_type eq 'ARRAY') {
    50          
715 0     0   0 validate (parameters => sub { scalar @$container_ref == 0 } => $container_ref => sub { "$message_text: [ '".join ("', '", @$container_ref)."' ]"; });
  0         0  
  0         0  
716 0         0 return scalar @$container_ref;
717             }
718             elsif ($container_type eq 'HASH') {
719 12         26 my @arg_names = keys %$container_ref;
720 12     12   77 validate (parameters => sub { scalar @arg_names == 0 } => $container_ref => sub { "$message_text: [ '".join ("', '", @arg_names)."' ]"; });
  12         51  
  0         0  
721 12         62 return scalar @arg_names;
722             }
723              
724 0     0   0 _do_fail("parameters_end()", sub { "unknown reference type $container_ref" });
  0         0  
725 0         0 return -1;
726             }
727              
728             # ------------------------------------------------------------------------------
729             #
730             # Messages and Validation Mode
731             #
732             # ------------------------------------------------------------------------------
733              
734             sub validation_trouble {
735 5   50 5 0 2804 my $trouble_accepted = shift || 0;
736 5 50       31 return $trouble_level > $trouble_accepted ? $trouble_level: 0;
737             }
738              
739             sub validation_messages {
740 41   100 41 0 161 my $mode = shift || '';
741              
742 41 100 66     270 return $message_store if !$message_store || !$mode || $mode ne '-clear';
      66        
743              
744 2         7 my @messages = @$message_store;
745 2         5 @$message_store = ();
746 2         16 return \@messages;
747             }
748              
749             sub prepare_validation_mode {
750 8     8 0 166 my $mode = lc(shift);
751              
752 8         13 my $new_fail_action = $fail_action;
753 8         15 my $new_off = $off;
754              
755 8 50       43 unless (is_valid(mode => -Enum => [ qw (die warn silent off) ] => $mode)) {
756 0         0 $trouble_level++;
757 0         0 croak "prepare_validation_mode(): unknown mode for Scalar::Validation selected: '$mode'";
758             }
759              
760             # print "#### Select validation mode: $mode\n";
761            
762 8 50       48 if ($mode eq 'die') {
    100          
    100          
    50          
763 0         0 $new_fail_action = $croak_sub;
764 0         0 $new_off = 0;
765             }
766             elsif ($mode eq 'warn') {
767 3     4   9 $new_fail_action = sub { carp "Warning: ", @_; return undef; };
  4         65  
  4         5538  
768 3         6 $new_off = 0;
769             }
770             elsif ($mode eq 'silent') {
771 3     2   13 $new_fail_action = sub { return undef; };
  2         6  
772 3         7 $new_off = 0;
773             }
774             elsif ($mode eq 'off') {
775 2     0   8 $new_fail_action = sub { return undef; };
  0         0  
776 2         6 $new_off = 1;
777             } else {
778             # shouldn't be reached, just to be sure
779 0         0 $trouble_level++;
780 0         0 $fail_action->("prepare_validation_mode(): unknown validation mode $mode used");
781             }
782              
783 8         28 return $new_fail_action, $new_off;
784             }
785              
786             # ------------------------------------------------------------------------------
787             #
788             # Rules
789             #
790             # ------------------------------------------------------------------------------
791              
792             sub get_rules {
793 0     0 0 0 return $rule_store;
794             }
795              
796             sub rule_known {
797 2     2 0 5 my $rule = par (rule => Filled => shift, sub { "rule to search not set" });
  21     21   143  
798              
799 19 100       107 return $rule_store->{$rule} ? $rule : '';
800             }
801              
802             sub declare_rule {
803 2     2 1 6 my $rule_name = par (rule => Filled => shift, sub { "rule to declare not set" });
  18     18   295  
804 16 100       55 if (rule_known($rule_name)) { $fail_action->("rule '$rule_name': already defined"); }
  1         4  
805            
806 15         49 my %call_options = convert_to_named_params \@_;
807 15         26 my %rule_options;
808              
809             $rule_options{-where} = npar (-where => CodeRef => \%call_options
810 15     3   81 => sub { "rule '$rule_name': where condition"._defined_or_not_message($_, " is not a code reference: $_");});
  3         15  
811              
812             $rule_options{-message} = npar (-message => -Optional => CodeRef => \%call_options
813 0     0   0 => sub { "rule '$rule_name': message"._defined_or_not_message($_, " is not a code reference: $_");})
814 12   100 1   108 || sub { "Value $_ is not valid for rule '$rule_name'" };
  1         5  
815              
816 12         78 $rule_options{-as} = npar (-as => -Optional => String => \%call_options);
817 12         50 $rule_options{-enum} = npar (-enum => -Optional => HashRef => \%call_options);
818 12         51 $rule_options{-name} = npar (-name => -Default => $rule_name => String => \%call_options);
819 12         56 $rule_options{-description } = npar (-description => -Default => "Rule $rule_name" => String => \%call_options);
820 12         60 $rule_options{-owner} = npar (-owner => -Default => 'CPAN' => String => \%call_options);
821            
822 12         40 parameters_end (\%call_options);
823            
824 12         36 $rule_store->{$rule_name} = \%rule_options;
825            
826 12         79 return $rule_name;
827             }
828              
829             sub delete_rule {
830 0     0 1 0 my $rule_name = par (rule => Filled => shift, sub { "rule to delete not set" });
  2     2   12  
831              
832 0     0   0 validate (delete_rule => Defined => delete $rule_store->{$rule_name}
833 2         29 => sub {"no rule $rule_name found to delete"});
834 2         20 return $rule_name;
835             }
836              
837             sub replace_rule {
838 0     0 1 0 my $rule_name = par (rule => Filled => shift, sub { "rule to replace not set" });
  1     1   6  
839              
840 1         7 return declare_rule(delete_rule($rule_name), @_);
841             }
842              
843             # $_ is set to string '' in message part, if it was not defined
844             sub _defined_or_not_message {
845 3 100   3   8 return " is missing" if '' eq shift;
846 2         7 return shift;
847             }
848              
849             # ------------------------------------------------------------------------------
850             #
851             # Dynamic rules
852             #
853             # ------------------------------------------------------------------------------
854              
855             # --- Enum ---------------------------------------------------------------------------
856              
857             sub Enum {
858 1     1 0 3 my $rule_name = shift;
859 1         3 my %enums = map { $_ => 1 } @_;
  6         17  
860 1         4 my @enums_list = @_;
861              
862             return ($rule_name,
863 3 50   3   30 -where => sub { defined $_ && defined $enums{$_} },
864             -enum => \%enums,
865 1     1   9 -message => sub { "$rule_name: value $_ unknown, allowed values are: [ ".join (", ", @enums_list)." ]" }
866 1         13 );
867             }
868              
869             sub enum {
870 1     1 0 3 my $rule_name = shift;
871 1         3 my %enums = map { lc($_) => 1 } @_;
  6         20  
872 1         5 my @enums_list = map { lc($_) } @_;
  6         14  
873              
874             return ($rule_name,
875 3 50   3   30 -where => sub { defined $_ && defined $enums{lc($_)} },
876             -enum => \%enums,
877 1     1   9 -message => sub { "$rule_name: value $_ unknown, allowed values (transformed to lower case) are: [ ".join (", ", @enums_list)." ]" }
878 1         17 );
879             }
880              
881             sub Enum_explained {
882 1     1 0 12 _handle_enum_explained(undef, "", @_);
883             }
884              
885             sub enum_explained {
886 9     9 0 39 _handle_enum_explained(sub { lc($_[0])}, " (transformed to lower case)", @_);
  1     1   612  
887             }
888              
889             # --- numerical compare ---------------------------------------------------------------------------
890              
891             sub greater_than {
892 4     4 0 6 my $limit = shift;
893 4         8 my $type = shift;
894             return ({ -as => $type,
895 4     4   17 -where => sub { $_ > $limit },
896 2     2   7 -message => sub { "$_ > $limit failed. Value is not of type $type or not greater than limit."},
897             },
898 4         36 @_);
899             }
900              
901             sub greater_equal {
902 6     6 0 10 my $limit = shift;
903 6         10 my $type = shift;
904             return ({ -as => $type,
905 6     6   24 -where => sub { $_ >= $limit },
906 2     2   8 -message => sub { "$_ >= $limit failed. Value is not of type $type or not greater than limit."},
907             },
908 6         55 @_);
909             }
910              
911             sub equal_to {
912 3     3 0 7 my $compare = shift;
913 3         5 my $type = shift;
914 3 100       8 if ($type eq 'String') {
915             return ({ -as => $type,
916 1     1   4 -where => sub { $_ eq $compare },
917 0     0   0 -message => sub { "$_ eq $compare failed. Value is not of type $type or different."},
918             },
919 1         12 @_);
920             }
921            
922             return ({ -as => $type,
923 2     2   87 -where => sub { $_ == $compare },
924 0     0   0 -message => sub { "$_ == $compare failed. Value is not of type $type or different."},
925             },
926 2         22 @_);
927             }
928              
929             sub less_than {
930 4     4 0 8 my $limit = shift;
931 4         7 my $type = shift;
932             return ({ -as => $type,
933 4     4   19 -where => sub { $_ < $limit },
934 2     2   8 -message => sub { "$_ < $limit failed. Value is not of type $type or not less than limit."},
935             },
936 4         44 @_);
937             }
938              
939             sub less_equal {
940 6     6 0 14 my $limit = shift;
941 6         11 my $type = shift;
942             return ({ -as => $type,
943 6     6   30 -where => sub { $_ <= $limit },
944 2     2   9 -message => sub { "$_ <= $limit failed. Value is not of type $type or not less than limit."},
945             },
946 6         68 @_);
947             }
948              
949             # --- ISA ---------------------------------------------------------------------------
950              
951             sub is_a {
952 8     8 0 71 my $type = shift;
953             return ({ -as => 'Class',
954 2     2   34 -where => sub { return $_->isa($type) },
955 7     7   26 -message => sub { "$_ is not of class $type or derived from it."},
956             },
957 8         138 @_);
958             }
959              
960             # ------------------------------------------------------------------------------
961             #
962             # Validation
963             #
964             # ------------------------------------------------------------------------------
965              
966             # --- helpful for tests ------------------------------------------------
967              
968             sub is_valid {
969 277     277 1 369 my $valid = 1;
970              
971 277     67   975 local $fail_action = sub { $valid = 0 };
  67         104  
972 277         386 local $trouble_level = 0; # not to rise trouble level
973            
974 277         592 validate(@_);
975              
976 277         1203 return $valid;
977             }
978              
979             # --- return value if valid ---------------
980             # --- return corrected value if invalid ---------------
981             sub validate_and_correct {
982 8     8 1 22 my ($validation_options_ref, # options for validate
983             $options_ref
984             ) = @_;
985              
986 8         18 my $correction_action = $options_ref->{-correction}; # action that does corrections in value
987              
988 8         114 my $validation_options_copied = 0;
989 8         12 my $value_pos = 2;
990 8         18 my $special_rule = $special_rules->{$validation_options_ref->[1]};
991 8 100       26 $value_pos = $special_rule->{-value_position} if $special_rule;
992              
993 8 100       22 unless (defined $validation_options_ref->[$value_pos]) {
994 3         8 my $default = $options_ref->{-default};
995              
996 3 50 33     18 if (defined $default && $value_pos >= 0) {
997 3         7 my @tmp_validation_options = @$validation_options_ref;
998 3         6 $validation_options_ref = \@tmp_validation_options;
999 3         4 $validation_options_ref->[$value_pos] = $default;
1000 3         7 $validation_options_copied = 1;
1001             }
1002             }
1003              
1004 8 100       18 if ($correction_action) {
1005 6         10 my $orig_fail_action = $fail_action;
1006 6         6 my $correction_done = 0;
1007 6         8 my $result = undef;
1008             {
1009 6         8 local ($fail_action) = sub {
1010 5     5   24 s/^'//o;
1011 5         19 s/'$//o;
1012 5         9 $correction_done = 1;
1013 5         17 $correction_action->($_);
1014            
1015 6         23 };
1016 6         16 $result = validate(@$validation_options_ref);
1017             }
1018            
1019 6 100       14 if ($correction_done) {
1020             # --- update arg vector by new value $result ---
1021 5 50       15 if ($value_pos >= 0){
1022 5 100       13 unless ($validation_options_copied) {
1023 4         12 my @corrected_validation_options = @$validation_options_ref;
1024 4         8 $validation_options_ref = \@corrected_validation_options;
1025             }
1026 5         14 $validation_options_ref->[$value_pos] = $result;
1027             }
1028             }
1029             else {
1030 1 50       7 my $print_result = defined ($result) ? "'$result'" : '';
1031 1         7 return $result;
1032             }
1033             }
1034 7         20 return validate(@$validation_options_ref);
1035             }
1036              
1037             # --- don't name key twice, deletes validated values out of hash -------------------------
1038             # named_parameter
1039             sub named_parameter {
1040 90     90 1 122 my $first_arg = shift;
1041 90         92 my $hash_ref;
1042            
1043             my $msg_ref;
1044 0         0 my $key;
1045 0         0 my $option_args_ref;
1046              
1047 90         121 my $args_ref = \@_;
1048              
1049 90 100       195 unless (is_valid(key => Scalar => $first_arg)) {
1050 2         4 $args_ref = validate (validation_args => ArrayRef => $first_arg);
1051 2         5 $key = shift @$args_ref;
1052 2         3 $option_args_ref = shift;
1053             }
1054             else {
1055 88         152 $key = $first_arg;
1056             }
1057              
1058 90         154 $key = validate (key => Scalar => $key);
1059              
1060 90         130 $hash_ref = pop @$args_ref;
1061            
1062 90 100       166 unless (is_valid(option_ref => HashRef => $hash_ref)) {
1063 27         55 $msg_ref = validate (message_ref => CodeRef => $hash_ref);
1064 27         61 $hash_ref = validate (option_ref => HashRef => pop @$args_ref);
1065             }
1066              
1067 90         166 my $value = delete $hash_ref->{$key};
1068              
1069 90 100       198 unless (defined $value) {
1070 53 50       103 if ($option_args_ref) {
1071 0         0 $value = $option_args_ref->{-default};
1072 0         0 print "used default $key => '$value'\n";
1073             # print $option_args_ref->{-description}."\n";
1074             }
1075             }
1076              
1077 90         176 return validate ($key, @$args_ref, $value, $msg_ref);
1078             }
1079              
1080             # --- return value if valid ---------------
1081             # --- call $fail_action if invalid ---------------
1082             sub _do_validate {
1083 906 100   906   4495 if ($off) {
1084 6         9 my $value_pos = 2;
1085 6 100       26 $value_pos = $special_rules->{$_[1]}->{-value_position} if $special_rules->{$_[1]};
1086 6 50       26 return $_[$value_pos] if $value_pos >= 0;
1087             }
1088              
1089 900   100     2174 my $subject_info = shift || '';
1090 900         1104 my $rule_info = shift;
1091              
1092 900 50       1687 unless ($rule_info) {
1093 0         0 $trouble_level++;
1094 0         0 $fail_action->("rule for validation not set");
1095 0         0 return $_; # in case of fail action doesn't die
1096             }
1097              
1098 900         1436 my $rule_ref = $rule_store->{$rule_info};
1099              
1100 900 100       1698 unless ($rule_ref) {
1101 250         791 my $special_rule = $special_rules->{$rule_info}->{-code};
1102              
1103 250 100       788 return $special_rule->($subject_info, @_) if $special_rule;
1104              
1105 51         80 my $ref_type = ref ($rule_info);
1106            
1107 51 100       1032 unless ($ref_type) {
    100          
    50          
1108 2         5 $trouble_level++;
1109 2         12 $fail_action->("unknown rule '$rule_info' for validation");
1110 0         0 return shift; # in case of fail action doesn't die
1111             }
1112             elsif ($ref_type eq 'HASH') { # given rule
1113 34         60 $rule_ref = $rule_info;
1114             # TODO: validate rule ...
1115             }
1116             elsif ($ref_type eq 'CODE') { # where condition for rule
1117             $rule_ref = {
1118             -where => $rule_info,
1119 2     2   9 -message => sub { "$_ does not match free defined rule" },
1120 15         107 };
1121             }
1122             else {
1123 0         0 $trouble_level++;
1124 0         0 $fail_action->("Rules: cannot handle ref type '$ref_type' of rule '$rule_info' for validation");
1125 0         0 return shift; # in case of fail action doesn't die
1126             }
1127             }
1128              
1129 699         937 local $_ = shift;
1130 699         866 my $message_ref = shift;
1131              
1132 699         1370 my $orig_value = $_;
1133 699   66     2683 my $test_message_ref = $message_ref || $rule_ref->{-message};
1134              
1135 699 100       1754 my $parent_is_valid = defined $rule_ref->{-as}
1136             ? _check_parent_rules($rule_ref->{-as}, $_)
1137             : 1;
1138              
1139 698 100 100     2326 unless ($parent_is_valid && $rule_ref->{-where}->()) {
1140 105 100       2333 $_ = defined ($_) ? "'$_'" : '';
1141 105         184 my $message = $get_caller_info->()."($subject_info): ".$test_message_ref->();
1142 105 100       276 push (@$message_store, $message) if $message_store;
1143 105         131 $trouble_level++;
1144 105         265 my $result = $fail_action->($message);
1145 64 100       275 return $result if defined $result;
1146             }
1147              
1148 596         2065 return $orig_value;
1149             }
1150              
1151             1;
1152              
1153             __END__