File Coverage

blib/lib/Scalar/Validation.pm
Criterion Covered Total %
statement 287 421 68.1
branch 88 138 63.7
condition 21 35 60.0
subroutine 69 97 71.1
pod 4 28 14.2
total 469 719 65.2


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, Sat Oct 25 15:10:22 2014
8             #
9             # More documentation at the end of file
10             #------------------------------------------------------------------------------
11              
12             $VERSION = "0.700";
13              
14             package Scalar::Validation;
15              
16 1     1   32762 use base qw (Exporter);
  1         2  
  1         84  
17              
18 1     1   9 use strict;
  1         1  
  1         38  
19 1     1   6 use warnings;
  1         7  
  1         180  
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             p_start parameters_start convert_to_named_params parameters_end p_end
27             validation_trouble get_validation_trouble_level add_validation_trouble
28             validation_messages get_and_reset_validation_messages prepare_validation_mode
29             meta_info_clear build_meta_info_for_module end_meta_info_gen get_meta_info list_meta_info
30             );
31              
32             our %EXPORT_TAGS = (
33             all => [qw(validate is_valid validate_and_correct npar named_parameter par parameter
34             get_rules rule_known declare_rule delete_rule replace_rule enum Enum enum_explained Enum_explained
35             greater_than greater_equal less_than less_equal equal_to g_t g_e l_t l_e
36             is_a
37             p_start parameters_start convert_to_named_params parameters_end p_end
38             validation_trouble get_validation_trouble_level add_validation_trouble
39             validation_messages get_and_reset_validation_messages prepare_validation_mode
40             meta_info_clear build_meta_info_for_module end_meta_info_gen get_meta_info list_meta_info
41             )],);
42              
43 1     1   7 use Carp;
  1         2  
  1         9772  
44             # use Data::Dumper;
45              
46             # ------------------------------------------------------------------------------
47             #
48             # Initiliazation
49             #
50             # ------------------------------------------------------------------------------
51              
52             our $ignore_callers = { __PACKAGE__ , 1 };
53             our $ignore_caller_pattern;
54              
55             sub _update_caller_pattern {
56 1     1   73 $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             _update_caller_pattern();
69              
70             _init_private_calls();
71             _init_run_API();
72              
73             # _init_doc_API();
74              
75             # ------------------------------------------------------------------------------
76             #
77             # default actions, not changable
78             #
79             # ------------------------------------------------------------------------------
80              
81             our $call_info = '';
82              
83             my $croak_sub = sub { croak "Error: ",@_; };
84             my $get_caller_info_default = sub {
85             my ($module, $file_name, $line, $sub_name);
86             $sub_name = __PACKAGE__;
87             my $call_level_iter = 1;
88              
89             while ($sub_name =~ $ignore_caller_pattern) {
90             ($module, $file_name, $line, $sub_name) = caller($call_level_iter++);
91             $sub_name = '' unless $sub_name;
92             }
93            
94             $sub_name = "MAIN" unless $sub_name;
95             return $sub_name;
96             };
97              
98             # ------------------------------------------------------------------------------
99             #
100             # variables my be overwritten by user
101             #
102             # ------------------------------------------------------------------------------
103              
104             our $message_store = undef; # local $Scalar::Validation::message_store = []; # to start storing messages
105             our $trouble_level = 0; # to count failed validations. Not affected by is_valid(...)
106             our $off = 0; # no validation checks if $off == 1
107             our $validate_defaults = 1; # validate default values defined by -Default rule, if set
108              
109             # ------------------------------------------------------------------------------
110             #
111             # actions, changable
112             #
113             # ------------------------------------------------------------------------------
114              
115             our $fail_action = $croak_sub;
116             our $get_caller_info = $get_caller_info_default;
117              
118             # ------------------------------------------------------------------------------
119             #
120             # private vars of Validation "Instance"
121             #
122             # ------------------------------------------------------------------------------
123             my $non_blessed = {
124             REF => 1,
125             ARRAY => 1,
126             HASH => 1,
127             };
128              
129             my $special_rules;
130             my $rule_store;
131             my $get_content_subs;
132              
133              
134             my $class_meta_model = {};
135             my $doc_class_name;
136             my $doc_class_method;
137              
138             # ------------------------------------------------------------------------------
139             #
140             # normal rules, can be added, replaced, removed
141             #
142             # ------------------------------------------------------------------------------
143              
144             $rule_store = {
145              
146             # --- This rules are needed for Validation.pm to work, don't delete or change! ---
147            
148             Defined => { -name => 'Defined',
149             -where => sub { defined $_ },
150             -message => sub { "value is not defined" },
151             -owner => 'CPAN',
152             -description => "Value is defined",
153             },
154             Filled => { -name => 'Filled',
155             -where => sub { defined $_ and ref($_) eq '' and $_ ne '' },
156             -message => sub { "value is not set" },
157             -owner => 'CPAN',
158             -description => "Value is Scalar and defined and not empty ('')",
159             },
160             Empty => { -name => 'Empty',
161             -where => sub { !defined $_ or $_ eq '' },
162             -message => sub { "value $_ has to be empty" },
163             -owner => 'CPAN',
164             -description => "Value is not defined or ''",
165             },
166             Optional => { -name => 'Optional',
167             -where => sub { 1; },
168             -message => sub { "value is optional" },
169             -owner => 'CPAN',
170             -description => "Value is optional, cannot fail. Use as last entry in -Or rule.",
171             },
172             String => { -name => 'String',
173             -where => sub { defined $_ and ref($_) eq '' },
174             -message => sub { "value $_ is not a string" },
175             -owner => 'CPAN',
176             -description => "Values is a Scalar and defined",
177             },
178             Int => { -name => 'Int',
179             -as => 'Filled',
180             -where => sub { /^[\+\-]?\d+$/ },
181             -message => sub { "value $_ is not an integer" },
182             -owner => 'CPAN',
183             -description => "Value is an integer",
184             },
185             Even => { -name => 'Even',
186             -as => 'Int',
187             -where => sub { $_ % 2 ? 0: 1; },
188             -message => sub { "value $_ is not an integer or not even"},
189             -owner => 'CPAN',
190             -description => 'Value is an even integer ($_ % 2 == 0)',
191             },
192             Scalar => { -name => 'Scalar',
193             -where => sub { ref($_) eq '' },
194             -message => sub { "value $_ is not a scalar" },
195             -owner => 'CPAN',
196             -description => 'Value is a Scalar : ref ($_) eq ""',
197             },
198             Ref => { -name => 'Ref',
199             -where => sub { $_ and ref($_) ne '' },
200             -message => sub { "value $_ is not a reference" },
201             -owner => 'CPAN',
202             -description => "Value is a reference and not a scalar.",
203             },
204             ArrayRef => { -name => 'ArrayRef',
205             -where => sub { $_ and ref($_) eq 'ARRAY' },
206             -message => sub { "value $_ is not a array reference" },
207             -owner => 'CPAN',
208             -description => "Value is an Array reference.",
209             },
210             HashRef => { -name => 'HashRef',
211             -where => sub { $_ and ref($_) eq 'HASH' },
212             -message => sub { "value $_ is not a hash reference" },
213             -owner => 'CPAN',
214             -description => "Value is a Hash reference.",
215             },
216             CodeRef => { -name => 'CodeRef',
217             -where => sub { $_ and ref($_) eq 'CODE' },
218             -message => sub { "value $_ is not a code reference" },
219             -owner => 'CPAN',
220             -description => "Value is a Code reference.",
221             },
222             Class => { -name => 'Class',
223             -where => sub { return 0 unless $_;
224             my $type_name = ref($_);
225             !$type_name || $non_blessed->{$type_name} ? 0: 1;
226             },
227             -message => sub { "value $_ is not a reference" },
228             -owner => 'CPAN',
229             -description => "Value is a reference and not a scalar.",
230             },
231             ModuleName => { -name => 'ModuleName',
232             -as => Filled =>
233             -where => sub { /^\w+(::\w+)*$/;
234             },
235             -message => sub { "value $_ is not a module name like A::Ba::Cba" },
236             -owner => 'CPAN',
237             -description => "Value is a module name like A::Ba::Cba.",
238             },
239              
240             # --- Some additional global rules --------------------
241            
242             ExistingFile => { -name => 'ExistingFile',
243             -as => 'Filled',
244             -where => sub { -f $_ },
245             -message => sub { "$_ is not a valid name of an existing file"},
246             -owner => 'CPAN',
247             -description => "File with given file name has to exist"
248             },
249              
250             Bool => { -name => 'Bool',
251             -where => sub { ref ($_) ? 0: 1 },
252             -message => sub { "value $_ is not a bool value" },
253             -owner => 'CPAN',
254             -description => "Value is a Scalar, all values including undef allowed",
255             },
256             PositiveInt => { -name => 'PositiveInt',
257             -as => 'Int',
258             -where => sub { $_ >= 0 },
259             -message => sub { "value $_ is not a positive integer" },
260             -owner => 'CPAN',
261             -description => "Value is a positive integer",
262             },
263             NegativeInt => { -name => 'NegativeInt',
264             -as => 'Int',
265             -where => sub { $_ < 0 },
266             -message => sub { "value $_ is not a negative integer" },
267             -owner => 'CPAN',
268             -description => "Value is a negative Integer",
269             },
270             Float => { -name => 'Float',
271             -as => 'Filled',
272             -where => sub { /^[\+\-]?\d+(\.\d+)?([Ee][\+-]?\d+)?$/ },
273             -message => sub { "value $_ is not a float" },
274             -owner => 'CPAN',
275             -description => "Value is a floating number with optional exponent",
276             },
277             PositiveFloat => { -name => 'PositiveFloat',
278             -as => 'Float',
279             -where => sub { $_ > 0 },
280             -message => sub { "value $_ is not a positive float" },
281             -owner => 'CPAN',
282             -description => "Value is a positive floating number with optional exponent",
283             },
284             NegativeFloat => { -name => 'NegativeFloat',
285             -as => 'Float',
286             -where => sub { $_ < 0 },
287             -message => sub { "value $_ is not a negative float" },
288             -owner => 'CPAN',
289             -description => "Value is a negative floating number with optional exponent",
290             },
291             };
292              
293             # ------------------------------------------------------------------------------
294             #
295             # gets content of ref container
296             #
297             # ------------------------------------------------------------------------------
298              
299             $get_content_subs = {
300             HASH => sub { my @keys = keys %$_; return scalar @keys ? \@keys: undef; },
301             ARRAY => sub { return scalar @$_ ? $_: undef; },
302             };
303              
304             # ------------------------------------------------------------------------------
305             #
306             # special rules (combined and other), not changable
307             #
308             # ------------------------------------------------------------------------------
309              
310             $special_rules = {
311             -Optional => {
312             -value_position => 3,
313             -code => sub {
314             # my $subject_info = shift || '';
315             # my $rule_info = shift;
316             local $_ = $_[2];
317              
318             # skip one param if special rule follows
319             my $special_rule = $special_rules->{$_[1]};
320             if ($special_rule) {
321             $_ = $_[$special_rule->{-value_position}];
322             }
323             ;
324            
325             return $_ if !defined $_; # value not set
326             return validate(@_);
327             },
328             -print_args => sub {
329             eval {
330             my $rule = shift;
331            
332             return "-Optional [$rule]";
333             }
334             }
335             },
336             -Default => {
337             -value_position => 4,
338             -code => sub {
339             my $subject_info = shift || '';
340             my $default = shift;
341             my $rule_info = shift;
342             # local $_ = $_[3];
343              
344             my $value_idx = 0;
345              
346             # --- skip x params if special rule follows ---
347             my $special_rule = $special_rules->{$rule_info};
348             if ($special_rule) {
349             my $special_idx = $special_rule->{-value_position};
350             $value_idx = $special_idx - 2 if $special_idx >= 0;
351             }
352              
353             my $value = $_[$value_idx];
354              
355             # --- value set, validate it ---
356             return validate($subject_info, $rule_info, @_)
357             if (defined $value && $value ne '');
358              
359             # --- value not set ----
360             unless (defined $default) {
361             $trouble_level++;
362             $fail_action->("Rules: Default value for rule -Default not set!");
363             return $_;
364             }
365              
366             if (ref($default) eq 'CODE') {
367             $default = $default->();
368             }
369              
370             return $default unless $validate_defaults;
371              
372             my @args = @_;
373            
374             $args[$value_idx] = $default;
375              
376             # --- default has also to be validated!! ---
377             return validate($subject_info, $rule_info, @args)
378             },
379             -print_args => sub {
380             eval {
381             my $default = shift;
382             my $rule = shift;
383            
384             return "-Default = '$default' [$rule]";
385             }
386             }
387             },
388             -And => {
389             -value_position => 3,
390             -code => sub {
391             my $subject_info = shift || '';
392             my $rule_list = shift;
393             local $_ = shift;
394             my $message_ref = shift;
395            
396             my $rule_exists = 0;
397             my $orig_value = $_;
398            
399             foreach my $rule (@$rule_list) {
400             if (!defined $rule || $rule eq '') {
401             $trouble_level++;
402             $fail_action->("Rules: rule for validation not set");
403             next # in case of fail action doesn't die
404             }
405            
406             if ($rule) {
407             my $special_rule = $special_rules->{$rule};
408            
409             if ($special_rule) {
410             $trouble_level++;
411             $fail_action->("Rules: cannot call any special rule inside of rule '-And'\n");
412             next;
413             }
414              
415             validate ($subject_info, $rule, $_, $message_ref);
416             $rule_exists = 1;
417             }
418             }
419            
420             $trouble_level++;
421             $fail_action->("Rules: No rule found in list to be validated") unless $rule_exists;
422            
423             return $orig_value;
424             },
425             -print_args => sub {
426             my $rule_list = shift;
427            
428             delete $rule_list->[$#$rule_list] unless $rule_list->[$#$rule_list];
429             my $arg_info = join (', ', @$rule_list);
430             $arg_info = 'no argument informations' unless $arg_info;
431              
432             return "-And [$arg_info]";
433             }
434             },
435             -Or => {
436             -value_position => 3,
437             -code => sub {
438             my $subject_info = shift || '';
439             my $rule_list = shift;
440             local $_ = shift;
441             my $message_ref = shift;
442            
443             my $rule_exists = 0;
444             my $orig_value = $_;
445            
446             foreach my $rule_info (@$rule_list) {
447             if (!defined $rule_info || $rule_info eq '') {
448             $trouble_level++;
449             $fail_action->("Rules: rule for validation not set");
450             next # in case of fail action doesn't die
451             }
452            
453             next unless $rule_info;
454            
455             my $rule_ref = $rule_store->{$rule_info};
456             unless ($rule_ref) {
457             my $special_rule = $special_rules->{$rule_info};
458            
459             if ($special_rule) {
460             $trouble_level++;
461             $fail_action->("Rules: cannot call any special rule inside of rule '-Or'\n");
462             next;
463             }
464              
465             my $ref_type = ref ($rule_info);
466            
467             unless ($ref_type) {
468             $trouble_level++;
469             $fail_action->("Rules: unknown rule '$rule_info' for validation");
470             next; # in case of fail action doesn't die
471             } elsif ($ref_type eq 'HASH') { # given rule
472             $rule_ref = $rule_info;
473             # TODO: validate rule ...
474             } elsif ($ref_type eq 'CODE') { # where condition for rule
475             $rule_ref = {
476             -where => $rule_info,
477             -message => sub { "$_ does not match free defined rule" },
478             };
479             } else {
480             $trouble_level++;
481             $fail_action->("Rules: cannot handle ref type '$ref_type' of rule '$rule_info' for validation");
482             next; # in case of fail action doesn't die
483             }
484             }
485              
486             if ($rule_ref) {
487             my $test_message_ref = $message_ref || $rule_ref->{-message};
488              
489             my $parent_is_valid = defined $rule_ref->{-as}
490             ? _check_parent_rules($rule_ref->{-as}, $_)
491             : 1;
492             return $orig_value if $parent_is_valid && $rule_ref->{-where}->();
493              
494             $rule_exists = 1;
495             }
496             }
497            
498             $trouble_level++;
499             $fail_action->("Rules: No rule found in list to be validated") unless $rule_exists;
500              
501             my $result = _do_fail($subject_info, $message_ref || sub { "No rule matched of [".join(', ', @$rule_list)."]";});
502             return $result if defined $result;
503              
504             return $orig_value;
505             },
506             -print_args => sub {
507             my $rule_list = shift;
508            
509             delete $rule_list->[$#$rule_list] unless $rule_list->[$#$rule_list];
510             my $arg_info = join (', ', @$rule_list);
511             $arg_info = 'no argument informations' unless $arg_info;
512              
513             return "-Or [$arg_info]";
514             }
515             },
516             -Enum => {
517             -value_position => 3,
518             -code => sub {
519             my $subject_info = shift || '';
520             my $enum_ref = shift;
521             local $_ = shift;
522             my $message_ref = shift;
523            
524             my $orig_value = $_;
525              
526             my $arg_type = ref ($enum_ref);
527             if ($arg_type eq 'ARRAY') {
528             $enum_ref = { map {$_=> 1} @$enum_ref };
529             }
530             elsif ($arg_type ne 'HASH') {
531             _do_fail($subject_info, sub {"-Enum needs HASH_ref as second parameter";});
532             }
533              
534             unless (defined $_ && $enum_ref->{$_}) {
535             my $result = _do_fail($subject_info, $message_ref ||
536             sub { "value $_ unknown, allowed values are: [ "
537             .join (", ", sort (keys(%$enum_ref)))." ]"; }
538             );
539             return $result if defined $result;
540              
541             }
542             return $orig_value;
543             },
544             -print_args => sub {
545             my $enum_ref = shift;
546              
547             my $arg_info = 'no argument informations';
548             my $arg_type = ref ($enum_ref);
549             if ($arg_type eq 'ARRAY') {
550             $arg_info = join (', ', @$enum_ref);
551             }
552             elsif ($arg_type eq 'HASH') {
553             $arg_info = join (', ', sort(keys(%$enum_ref)));
554             }
555             return "Enum [$arg_info]";
556             }
557             },
558             -Range => {
559             -value_position => 4,
560             -code => sub {
561             my $subject_info = shift || '';
562             my $range_ref = shift;
563             my $rule = shift;
564             local $_ = shift;
565             my $message_ref = shift;
566            
567             my $wrong_call_message_sub_ref
568             = sub { "-Range needs ARRAY_ref containing two values [min max] as second parameter" };
569              
570             my $orig_value = $_;
571            
572             unless (ref($range_ref) eq 'ARRAY') {
573             _do_fail($subject_info, $wrong_call_message_sub_ref);
574             return $orig_value;
575             }
576              
577             unless (scalar @$range_ref == 2) {
578             _do_fail($subject_info, $wrong_call_message_sub_ref);
579             return $orig_value;
580             }
581              
582             my ($min, $max) = @$range_ref;
583             if ($min > $max) {
584             _do_fail($subject_info, sub { "(min) $min > $max (max) in range definition"; });
585             return $orig_value;
586             }
587              
588             # type check by is_valid to return here if fails
589             my @messages;
590             my $is_valid;
591             {
592             local ($message_store) = [];
593             $is_valid = is_valid ($subject_info, $rule, $_, $message_ref);
594             @messages = @{validation_messages()};
595             }
596            
597             unless ($is_valid) {
598             my $message = join ("\n", @messages);
599             push (@$message_store, $message) if $message_store;
600             $trouble_level++;
601             my $result = $fail_action->($message);
602             return $result if defined $result;
603              
604             return $orig_value;
605             }
606            
607             unless ($min <= $_ && $_<= $max) {
608             my $result = _do_fail($subject_info, sub {"value $_ is out of range [$min,$max]"});
609             return $result if defined $result;
610             return $orig_value;
611             }
612              
613             return $orig_value;
614             },
615             -print_args => sub {
616             my $range_ref = shift;
617             my $rule = shift;
618             my $min = $range_ref->[0];
619             my $max = $range_ref->[1];
620             return "Range [$min,$max] of type $rule";
621             }
622             },
623             -RefEmpty => {
624             -value_position => 3,
625             -code => sub {
626            
627             my $subject_info = shift || '';
628             local $_ = shift;
629             my $message_ref = shift;
630            
631             my $content_ref = _ref_empty_check($subject_info, $_, $message_ref);
632            
633             return undef unless defined $content_ref;
634            
635             my $count_results = scalar @$content_ref;
636             return 0 unless $count_results;
637            
638             _do_fail($subject_info, sub { "Should be empty, but contains $count_results entries: [ ".
639             join (", ", @$content_ref)." ];" });
640            
641             return $count_results;
642             },
643             -print_args => sub {
644             return "EmptyReference";
645             }
646             },
647             };
648              
649             # ------------------------------------------------------------------------------
650             #
651             # internal Methods
652             #
653             # ------------------------------------------------------------------------------
654              
655             sub _handle_enum_explained {
656 2     2   4 my $transform_key_ref = shift;
657 2         4 my $transformed_text = shift;
658 2         3 my $rule_name = shift;
659 2         3 my @enum_args;
660             my @enums_list;
661 0         0 my %enums;
662            
663 2         4 foreach my $arg (@_) {
664 24 100 66     73 if ($arg eq 1 or $arg eq 0) {
665             # arg is complete
666 6         7 my $last_idx = $#enum_args;
667              
668 6 50       13 if ($last_idx < 1) {
669 0         0 $trouble_level++;
670 0         0 $fail_action->("Rules: not enough configuration values for enum '$enum_args[0]'");
671             }
672            
673 6         8 my $explanation = $enum_args[$last_idx];
674 6 100       11 map { my $key = $transform_key_ref ? $transform_key_ref->($_): $_;
  12         22  
675 12         17 $enums{$key} = $explanation;
676 12         27 push (@enums_list, $key);
677             } @enum_args[0..--$last_idx];
678 6         13 @enum_args = ();
679             }
680             else {
681 18         24 push (@enum_args, $arg);
682             }
683             }
684              
685             my $validation_sub_ref = $transform_key_ref
686 3 50   3   22 ? sub { defined $_ && defined $enums{$transform_key_ref->($_)} }
687 2 50   3   15 : sub { defined $_ && defined $enums{$_} };
  3 100       22  
688            
689             return ($rule_name,
690             -where => $validation_sub_ref,
691             -enum => \%enums,
692 2     2   13 -message => sub { "$rule_name: value $_ unknown, allowed values$transformed_text ".
693             "are: [ ".join (", ", @enums_list)." ]" }
694 2         17 );
695             }
696              
697             sub _check_parent_rules {
698 226     226   226 my $rule_name = shift;
699 226         201 local $_ = shift;
700              
701 226         171 my $orig_value = $_;
702              
703 226         245 my $rule_ref = $rule_store->{$rule_name};
704              
705 226 100       341 unless ($rule_ref) {
706 1         3 $trouble_level++;
707 1         7 $fail_action->("Rules: unknown rule '$rule_name' for validation");
708 0         0 return 0; # in case of fail action doesn't die
709             }
710            
711 225 100       463 if (defined $rule_ref->{-as}) {
712 64 50       109 return 0 unless _check_parent_rules($rule_ref->{-as}, $_);
713             }
714            
715 225         411 return $rule_ref->{-where}->();
716             }
717              
718             sub _ref_empty_check {
719 5   50 5   9 my $subject_info = shift || '';
720 5         6 local $_ = shift;
721 5         6 my $message_ref = shift;
722              
723 5         6 my $ref_type = ref($_);
724              
725 5 100       9 unless ($ref_type) {
726 2     2   9 _do_fail($subject_info, sub { "Not a reference: $_" });
  2         5  
727 0         0 return undef;
728             }
729            
730 3         6 my $get_contents_ref = $get_content_subs->{$ref_type};
731              
732 3 100       7 unless ($get_contents_ref) {
733 1     1   8 _do_fail($subject_info, sub { "could not check, if $ref_type is empty" });
  1         3  
734 0         0 return undef;
735             }
736            
737 2         5 return $get_contents_ref->();
738             }
739              
740             sub _do_fail {
741 24     24   32 my $subject_info = shift;
742 24         24 my $message_ref = shift;
743              
744 24         21 $trouble_level++;
745              
746 24 100       94 $_ = defined ($_) ? "'$_'" : '';
747              
748 24         39 my $message = $get_caller_info->()."($call_info$subject_info): ".$message_ref->();
749 24 50       54 push (@$message_store, $message) if $message_store;
750              
751 24         40 return $fail_action->($message);
752             }
753              
754             # ------------------------------------------------------------------------------
755             #
756             # API Methods
757             #
758             # ------------------------------------------------------------------------------
759              
760             sub _init_run_API {
761 1     1   2 *p_start = *_do_parameters_start;
762 1         1 *parameters_start = *_do_parameters_start;
763              
764 1         2 *p_end = *_do_parameters_end;
765 1         1 *parameters_end = *_do_parameters_end;
766              
767 1         1 *npar = *_do_named_parameter;
768 1         2 *named_parameter = *_do_named_parameter;
769              
770 1         1 *parameter = *_do_validate_parameter;
771 1         1 *par = *_do_validate_parameter;
772              
773 1         1 *validate = *_do_validate_call;
774 1         2 *is_valid = *_do_is_valid;
775              
776 1         7 *get_api_doc = *get_api_documentation;
777              
778 1         2 *g_t = *greater_than;
779 1         1 *g_e = *greater_equal;
780 1         1 *l_t = *less_than;
781 1         1 *l_e = *less_equal;
782              
783             }
784              
785             sub _init_private_calls {
786 1     1   3 *_p_npar = *_do_named_parameter;
787 1         1 *_p_named_parameter = *_do_named_parameter;
788            
789 1         2 *_p_parameter = *_do_validate_parameter;
790 1         1 *_p_par = *_do_validate_parameter;
791            
792 1         2 *_p_validate = *_do_validate_call;
793 1         1 *_p_is_valid = *_do_is_valid;
794             }
795              
796             sub _get_meta_extraction_code {
797 0     0   0 my $module_name = shift;
798 0         0 return "
799             use $module_name;
800             *".$module_name."::p_start = *".__PACKAGE__."::_start_sub_meta_extraction;
801             *".$module_name."::parameters_start = *".__PACKAGE__."::_start_sub_meta_extraction;
802            
803             *".$module_name."::p_end = *".__PACKAGE__."::_end_sub_meta_extraction;
804             *".$module_name."::parameters_end = *".__PACKAGE__."::_end_sub_meta_extraction;
805            
806             *".$module_name."::npar = *".__PACKAGE__."::_sub_meta_extract_named_parameter;
807             *".$module_name."::named_parameter = *".__PACKAGE__."::_sub_meta_extract_named_parameter;
808            
809             *".$module_name."::parameter = *".__PACKAGE__."::_sub_meta_extract_positional_parameter;
810             *".$module_name."::par = *".__PACKAGE__."::_sub_meta_extract_positional_parameter;
811            
812             *".$module_name."::validate = *".__PACKAGE__."::_sub_meta_no_extraction;
813             ";
814             }
815              
816             sub _reset_validation_code {
817 0     0   0 my $module_name = shift;
818 0         0 return "
819             use $module_name;
820             *".$module_name."::p_start = *".__PACKAGE__."::_do_parameters_start;
821             *".$module_name."::parameters_start = *".__PACKAGE__."::_do_parameters_start;
822            
823             *".$module_name."::p_end = *".__PACKAGE__."::_do_parameters_end;
824             *".$module_name."::parameters_end = *".__PACKAGE__."::_do_parameters_end;
825            
826             *".$module_name."::npar = *".__PACKAGE__."::_do_named_parameter;
827             *".$module_name."::named_parameter = *".__PACKAGE__."::_do_named_parameter;
828            
829             *".$module_name."::parameter = *".__PACKAGE__."::_do_validate_parameter;
830             *".$module_name."::par = *".__PACKAGE__."::_do_validate_parameter;
831            
832             *".$module_name."::validate = *".__PACKAGE__."::_do_validate_call;
833             ";
834             }
835              
836             sub _do_parameters_start {
837 0     0   0 return $trouble_level;
838             }
839              
840             sub convert_to_named_params {
841 17     17 0 75 my $array_ref = _p_validate (args => ArrayRef => shift);
842              
843             _p_validate (arg_count => Even => scalar @$array_ref =>
844 16     1   50 sub { "Even number of args needed to build a hash, but arg-count = $_" });
  1         4  
845 15         70 return @$array_ref;
846             }
847              
848             sub _do_parameters_end {
849              
850 12     12   35 my $container_ref = _p_par (container_ref => -Or => [HashRef => 'ArrayRef'] => shift);
851 12   50     30 my $message_text = _p_par (message_text => Scalar => shift) || "extra parameters found";
852              
853 12         14 my $container_type = ref ($container_ref);
854 12 50       32 if ($container_type eq 'ARRAY') {
    50          
855 0     0   0 _p_validate (parameters => sub { scalar @$container_ref == 0 } => $container_ref => sub { "$message_text: [ '".join ("', '", @$container_ref)."' ]"; });
  0         0  
  0         0  
856 0         0 return scalar @$container_ref;
857             }
858             elsif ($container_type eq 'HASH') {
859 12         24 my @arg_names = keys %$container_ref;
860 12     0   55 _p_validate (parameters => sub { scalar @arg_names == 0 } => $container_ref => sub { "$message_text: [ '".join ("', '", @arg_names)."' ]"; });
  12         37  
  0         0  
861 12         37 return scalar @arg_names;
862             }
863              
864 0     0   0 _do_fail("parameters_end()", sub { "unknown reference type $container_ref" });
  0         0  
865 0         0 return -1;
866             }
867              
868             # ------------------------------------------------------------------------------
869             #
870             # Meta Information Mode
871             #
872             # ------------------------------------------------------------------------------
873              
874             sub meta_info_clear {
875 0     0 0 0 print "# Start API documentation ==============================\n";
876 0         0 $class_meta_model = {};
877             }
878              
879             sub build_meta_info_for_module {
880 0     0 0 0 end_meta_info_gen();
881 0         0 $doc_class_name = _p_par(module_name => ModuleName => shift);
882 0         0 my $instance_creator = _p_par(instance_creation => -Optional => CodeRef => shift);
883              
884             # --- run sub -------------------------------------------------
885              
886             # print "# Module: $doc_class_name; # ==============================\n";
887 0         0 $class_meta_model->{$doc_class_name} = {};
888 0         0 $class_meta_model->{$doc_class_name}->{subs} = {};
889 0 0       0 eval (_get_meta_extraction_code($doc_class_name)); print $@ if $@;
  0         0  
890            
891 0 0       0 $instance_creator = eval ('sub { return '.$doc_class_name.'->new(); }')
892             unless $instance_creator;
893              
894 0         0 return $instance_creator->();
895             }
896              
897             sub end_meta_info_gen {
898 0 0   0 0 0 eval (_reset_validation_code($doc_class_name)) if $doc_class_name; print $@ if $@;
  0 0       0  
899             }
900              
901             sub get_meta_info {
902 0     0 0 0 return $class_meta_model;
903             }
904              
905             sub list_meta_info {
906 0     0 0 0 my @meta_info_list;
907              
908 0         0 foreach my $pm (sort (keys (%$class_meta_model))) {
909 0         0 my $subs = $class_meta_model->{$pm}->{subs};
910              
911 0         0 foreach my $sub_name (sort (keys (%$subs))) {
912 0         0 my $parameters = $subs->{$sub_name}->{params};
913 0         0 my %sub_info = (module => $pm,
914             sub => $sub_name);
915              
916 0 0       0 if (scalar (@$parameters)) {
917 0         0 foreach my $parameter (@$parameters) {
918 0         0 my %par_info = (%$parameter, %sub_info);
919 0         0 push (@meta_info_list, \%par_info);
920             }
921             }
922             else { # sub has no parameters
923 0         0 $sub_info{rule} = $sub_info{kind} = $sub_info{name} = '';
924 0         0 push (@meta_info_list, \%sub_info);
925             }
926             }
927             }
928              
929 0         0 return \@meta_info_list;
930             }
931              
932             sub _start_sub_meta_extraction { # parameters_start
933 0     0   0 $doc_class_method = $get_caller_info->();
934 0         0 $doc_class_method =~ s/.*\:\://og;
935 0         0 $class_meta_model->{$doc_class_name}->{subs}->{$doc_class_method} = {};
936 0         0 $class_meta_model->{$doc_class_name}->{subs}->{$doc_class_method}->{params} = [];
937             # print "\tMethod: $doc_class_method # ---------------------------------------\n";
938 0         0 return 0;
939             }
940              
941             sub _end_sub_meta_extraction { # parameters_end
942             # print "\t# --- end method documentation ---\n";
943 0     0   0 $trouble_level++;
944 0         0 return 0;
945             }
946              
947             sub _sub_meta_extract_named_parameter { # named_parameter
948 0     0   0 my $argument_infos = _api_doc_get_argument_info(@_);
949 0         0 push (@{$class_meta_model->{$doc_class_name}->{subs}->{$doc_class_method}->{params}},
  0         0  
950             {
951             kind => Named =>
952             name => $argument_infos->[0],
953             rule => $argument_infos->[1],
954             }
955             );
956              
957 0         0 local ($Scalar::Validation::off) = 1;
958 0         0 $trouble_level++;
959 0         0 return _do_named_parameter(@_);
960             }
961              
962             sub _sub_meta_extract_positional_parameter { # parameter
963 0     0   0 my $argument_infos = _api_doc_get_argument_info(@_);
964             # print "\t\tPositional Parameter: $argument_info_str\n";
965 0         0 push (@{$class_meta_model->{$doc_class_name}->{subs}->{$doc_class_method}->{params}},
  0         0  
966             {
967             kind => Positional =>
968             name => $argument_infos->[0],
969             rule => $argument_infos->[1],
970             }
971             );
972              
973 0         0 local ($Scalar::Validation::off) = 1;
974 0         0 $trouble_level++;
975 0         0 return _do_validate_parameter(@_);
976             }
977              
978             sub _sub_meta_no_extraction { # validate
979             # print "_sub_meta_no_extraction ".join (', ', @_)."\n";
980 0     0   0 local ($Scalar::Validation::off) = 1;
981 0         0 $trouble_level++;
982 0         0 return _do_validate_call(@_);
983             }
984              
985             sub _api_doc_get_argument_info {
986 0     0   0 my $name = shift;
987 0         0 my $rule_info = shift;
988              
989 0 0       0 unless ($rule_info) {
990 0         0 $trouble_level++;
991 0         0 $fail_action->("rule for validation not set");
992 0         0 return [$_]; # in case of fail action doesn't die
993             }
994              
995 0         0 my $rule_ref = $rule_store->{$rule_info};
996              
997 0         0 my $rule_info_string = 'No info for rule';
998              
999 0 0       0 if ($rule_ref) {
1000 0         0 $rule_info_string = $rule_ref->{-name};
1001             }
1002             else {
1003 0         0 my $special_rule_name = $rule_info;
1004             # print "$rule_info, ".join(', ', @_)."\n";
1005 0         0 $rule_ref = $special_rules->{$special_rule_name};
1006 0 0       0 if ($rule_ref) {
1007 0         0 my $last_idx = scalar(@_);
1008 0 0       0 if ($last_idx >= 0) {
1009 0         0 my $print_args = $rule_ref->{-print_args};
1010 0 0       0 if ($print_args) {
1011 0         0 $rule_info_string = $print_args->(@_);
1012             }
1013             else {
1014 0         0 $fail_action->("print_args missing for rule '$special_rule_name'");
1015             }
1016             }
1017             }
1018             }
1019              
1020 0 0       0 unless ($rule_ref) {
1021 0         0 my $ref_type = ref ($rule_info);
1022            
1023 0 0       0 unless ($ref_type) {
    0          
    0          
1024 0         0 $trouble_level++;
1025 0         0 my $error_message = "unknown rule '$rule_info' for validation";
1026 0         0 $fail_action->($error_message);
1027 0         0 return [$error_message];
1028             }
1029             elsif ($ref_type eq 'HASH') { # given rule
1030 0         0 $rule_ref = $rule_info;
1031             # TODO: _p_validate rule ...
1032             }
1033             elsif ($ref_type eq 'CODE') { # where condition for rule
1034 0         0 $rule_ref = {
1035             -name => 'anonymous private rule'
1036             };
1037             }
1038             else {
1039 0         0 $trouble_level++;
1040 0         0 my $error_message = "Rules: cannot handle ref type '$ref_type' of rule '$rule_info' for validation";
1041 0         0 $fail_action->$error_message();
1042 0         0 return [$error_message]; # in case of fail action doesn't die
1043             }
1044 0         0 $rule_info_string = $rule_ref->{-name};
1045             }
1046              
1047 0         0 return [$name, $rule_info_string];
1048              
1049             }
1050              
1051             # ------------------------------------------------------------------------------
1052             #
1053             # Messages and Validation Mode
1054             #
1055             # ------------------------------------------------------------------------------
1056              
1057             sub add_validation_trouble {
1058 0     0 0 0 my ($value
1059             ) = @_;
1060              
1061 0   0     0 $trouble_level += ($value || 1);
1062             }
1063              
1064             sub get_validation_trouble_level {
1065 0     0 0 0 return $trouble_level;
1066             }
1067              
1068             sub validation_trouble {
1069 5   50 5 0 994 my $trouble_accepted = shift || 0;
1070 5 50       27 return $trouble_level > $trouble_accepted ? $trouble_level: 0;
1071             }
1072              
1073             sub validation_messages {
1074 39   100 39 0 125 my $mode = shift || '';
1075              
1076 39 100 66     229 return $message_store if !$message_store || !$mode || $mode ne '-clear';
      66        
1077              
1078 2         6 my @messages = @$message_store;
1079 2         4 @$message_store = ();
1080 2         13 return \@messages;
1081             }
1082              
1083             sub prepare_validation_mode {
1084 7     7 0 115 my $mode = lc(shift);
1085              
1086 7         9 my $new_fail_action = $fail_action;
1087 7         7 my $new_off = $off;
1088              
1089 7 50       30 unless (_p_is_valid(mode => -Enum => [ qw (die warn silent off) ] => $mode)) {
1090 0         0 $trouble_level++;
1091 0         0 croak "prepare_validation_mode(): unknown mode for Scalar::Validation selected: '$mode'";
1092             }
1093              
1094             # print "#### Select validation mode: $mode\n";
1095            
1096 7 50       28 if ($mode eq 'die') {
    100          
    100          
    50          
1097 0         0 $new_fail_action = $croak_sub;
1098 0         0 $new_off = 0;
1099             }
1100             elsif ($mode eq 'warn') {
1101 2     2   6 $new_fail_action = sub { carp "Warning: ", @_; return undef; };
  2         21  
  2         1330  
1102 2         3 $new_off = 0;
1103             }
1104             elsif ($mode eq 'silent') {
1105 3     2   9 $new_fail_action = sub { return undef; };
  2         3  
1106 3         3 $new_off = 0;
1107             }
1108             elsif ($mode eq 'off') {
1109 2     0   6 $new_fail_action = sub { return undef; };
  0         0  
1110 2         3 $new_off = 1;
1111             } else {
1112             # shouldn't be reached, just to be sure
1113 0         0 $trouble_level++;
1114 0         0 $fail_action->("prepare_validation_mode(): unknown validation mode $mode used");
1115             }
1116              
1117 7         18 return $new_fail_action, $new_off;
1118             }
1119              
1120             # ------------------------------------------------------------------------------
1121             #
1122             # Rules
1123             #
1124             # ------------------------------------------------------------------------------
1125              
1126             sub get_rules {
1127 0     0 0 0 return $rule_store;
1128             }
1129              
1130             sub rule_known {
1131 2     2 0 3 my $rule = _p_par (rule => Filled => shift, sub { "rule to search not set" });
  21     21   123  
1132              
1133 19 100       69 return $rule_store->{$rule} ? $rule : '';
1134             }
1135              
1136             sub declare_rule {
1137 2     2 1 3 my $rule_name = _p_par (rule => Filled => shift, sub { "rule to declare not set" });
  18     18   266  
1138 16 100       47 if (rule_known($rule_name)) { $fail_action->("rule '$rule_name': already defined"); }
  1         4  
1139            
1140 15         35 my %call_options = convert_to_named_params \@_;
1141 15         20 my %rule_options;
1142              
1143             $rule_options{-where} = _p_npar (-where => CodeRef => \%call_options
1144 15     3   75 => sub { "rule '$rule_name': where condition"._defined_or_not_message($_, " is not a code reference: $_");});
  3         11  
1145              
1146             $rule_options{-message} = _p_npar (-message => -Optional => CodeRef => \%call_options
1147 0     0   0 => sub { "rule '$rule_name': message"._defined_or_not_message($_, " is not a code reference: $_");})
1148 12   100 1   71 || sub { "Value $_ is not valid for rule '$rule_name'" };
  1         8  
1149              
1150 12         62 $rule_options{-as} = _p_npar (-as => -Optional => String => \%call_options);
1151 12         36 $rule_options{-enum} = _p_npar (-enum => -Optional => HashRef => \%call_options);
1152 12         36 $rule_options{-name} = _p_npar (-name => -Default => $rule_name => String => \%call_options);
1153 12         43 $rule_options{-description } = _p_npar (-description => -Default => "Rule $rule_name" => String => \%call_options);
1154 12         36 $rule_options{-owner} = _p_npar (-owner => -Default => 'CPAN' => String => \%call_options);
1155              
1156 12         30 parameters_end (\%call_options);
1157            
1158 12         22 $rule_store->{$rule_name} = \%rule_options;
1159            
1160 12         60 return $rule_name;
1161             }
1162              
1163             sub delete_rule {
1164 0     0 1 0 my $rule_name = _p_par (rule => Filled => shift, sub { "rule to delete not set" });
  2     2   9  
1165              
1166 0     0   0 _p_validate (delete_rule => Defined => delete $rule_store->{$rule_name}
1167 2         10 => sub {"no rule $rule_name found to delete"});
1168 2         14 return $rule_name;
1169             }
1170              
1171             sub replace_rule {
1172 0     0 1 0 my $rule_name = _p_par (rule => Filled => shift, sub { "rule to replace not set" });
  1     1   5  
1173              
1174 1         5 return declare_rule(delete_rule($rule_name), @_);
1175             }
1176              
1177             # $_ is set to string '' in message part, if it was not defined
1178             sub _defined_or_not_message {
1179 3 100   3   7 return " is missing" if '' eq shift;
1180 2         5 return shift;
1181             }
1182              
1183             # ------------------------------------------------------------------------------
1184             #
1185             # Dynamic rules
1186             #
1187             # ------------------------------------------------------------------------------
1188              
1189             # --- Enum ---------------------------------------------------------------------------
1190              
1191             sub Enum {
1192 1     1 0 2 my $rule_name = shift;
1193 1         3 my %enums = map { $_ => 1 } @_;
  6         10  
1194 1         4 my @enums_list = @_;
1195              
1196             return ($rule_name,
1197 3 50   3   26 -where => sub { defined $_ && defined $enums{$_} },
1198             -enum => \%enums,
1199 1     1   12 -message => sub { "$rule_name: value $_ unknown, allowed values are: [ ".join (", ", @enums_list)." ]" }
1200 1         11 );
1201             }
1202              
1203             sub enum {
1204 1     1 0 34 my $rule_name = shift;
1205 1         3 my %enums = map { lc($_) => 1 } @_;
  6         17  
1206 1         3 my @enums_list = map { lc($_) } @_;
  6         8  
1207              
1208             return ($rule_name,
1209 3 50   3   26 -where => sub { defined $_ && defined $enums{lc($_)} },
1210             -enum => \%enums,
1211 1     1   8 -message => sub { "$rule_name: value $_ unknown, allowed values (transformed to lower case) are: [ ".join (", ", @enums_list)." ]" }
1212 1         11 );
1213             }
1214              
1215             sub Enum_explained {
1216 1     1 0 6 _handle_enum_explained(undef, "", @_);
1217             }
1218              
1219             sub enum_explained {
1220 9     9 0 31 _handle_enum_explained(sub { lc($_[0])}, " (transformed to lower case)", @_);
  1     1   332  
1221             }
1222              
1223             # --- numerical compare ---------------------------------------------------------------------------
1224              
1225             sub greater_than {
1226 4     4 0 8 my $limit = shift;
1227 4         4 my $type = shift;
1228             return ({ -as => $type,
1229 4     4   17 -where => sub { $_ > $limit },
1230 2     2   6 -message => sub { "$_ > $limit failed. Value is not of type $type or not greater than limit."},
1231             },
1232 4         38 @_);
1233             }
1234              
1235             sub greater_equal {
1236 6     6 0 10 my $limit = shift;
1237 6         7 my $type = shift;
1238             return ({ -as => $type,
1239 6     6   20 -where => sub { $_ >= $limit },
1240 2     2   6 -message => sub { "$_ >= $limit failed. Value is not of type $type or not greater than limit."},
1241             },
1242 6         49 @_);
1243             }
1244              
1245             sub equal_to {
1246 3     3 0 6 my $compare = shift;
1247 3         3 my $type = shift;
1248 3 100       9 if ($type eq 'String') {
1249             return ({ -as => $type,
1250 1     1   4 -where => sub { $_ eq $compare },
1251 0     0   0 -message => sub { "$_ eq $compare failed. Value is not of type $type or different."},
1252             },
1253 1         12 @_);
1254             }
1255            
1256             return ({ -as => $type,
1257 2     2   9 -where => sub { $_ == $compare },
1258 0     0   0 -message => sub { "$_ == $compare failed. Value is not of type $type or different."},
1259             },
1260 2         20 @_);
1261             }
1262              
1263             sub less_than {
1264 4     4 0 8 my $limit = shift;
1265 4         4 my $type = shift;
1266             return ({ -as => $type,
1267 4     4   13 -where => sub { $_ < $limit },
1268 2     2   8 -message => sub { "$_ < $limit failed. Value is not of type $type or not less than limit."},
1269             },
1270 4         35 @_);
1271             }
1272              
1273             sub less_equal {
1274 6     6 0 9 my $limit = shift;
1275 6         8 my $type = shift;
1276             return ({ -as => $type,
1277 6     6   20 -where => sub { $_ <= $limit },
1278 2     2   7 -message => sub { "$_ <= $limit failed. Value is not of type $type or not less than limit."},
1279             },
1280 6         50 @_);
1281             }
1282              
1283             # --- ISA ---------------------------------------------------------------------------
1284              
1285             sub is_a {
1286 8     8 0 58 my $type = shift;
1287             return ({ -name => "IsClass '$type'",
1288             -as => 'Class',
1289 2     2   22 -where => sub { return $_->isa($type) },
1290 7     7   18 -message => sub { "$_ is not of class $type or derived from it."},
1291             },
1292 8         80 @_);
1293             }
1294              
1295             # ------------------------------------------------------------------------------
1296             #
1297             # Validation
1298             #
1299             # ------------------------------------------------------------------------------
1300              
1301             # --- helpful for tests ------------------------------------------------
1302              
1303             sub _do_is_valid {
1304 276     276   267 my $valid = 1;
1305              
1306 276     68   605 local $fail_action = sub { $valid = 0 };
  68         89  
1307 276         256 local $trouble_level = 0; # not to rise trouble level
1308            
1309 276         410 validate(@_);
1310              
1311 276         827 return $valid;
1312             }
1313              
1314             # --- return value if valid ---------------
1315             # --- return corrected value if invalid ---------------
1316             sub validate_and_correct {
1317 8     8 1 14 my ($validation_options_ref, # options for validate
1318             $options_ref
1319             ) = @_;
1320              
1321 8         15 my $correction_action = $options_ref->{-correction}; # action that does corrections in value
1322              
1323 8         7 my $validation_options_copied = 0;
1324 8         6 my $value_pos = 2;
1325 8         14 my $special_rule = $special_rules->{$validation_options_ref->[1]};
1326 8 100       16 $value_pos = $special_rule->{-value_position} if $special_rule;
1327              
1328 8 100       17 unless (defined $validation_options_ref->[$value_pos]) {
1329 3         6 my $default = $options_ref->{-default};
1330              
1331 3 50 33     27 if (defined $default && $value_pos >= 0) {
1332 3         9 my @tmp_validation_options = @$validation_options_ref;
1333 3         4 $validation_options_ref = \@tmp_validation_options;
1334 3         7 $validation_options_ref->[$value_pos] = $default;
1335 3         6 $validation_options_copied = 1;
1336             }
1337             }
1338              
1339 8 100       16 if ($correction_action) {
1340 6         8 my $orig_fail_action = $fail_action;
1341 6         4 my $correction_done = 0;
1342 6         6 my $result = undef;
1343             {
1344 6         7 local ($fail_action) = sub {
1345 5     5   15 s/^'//o;
1346 5         13 s/'$//o;
1347 5         3 $correction_done = 1;
1348 5         12 $correction_action->($_);
1349            
1350 6         17 };
1351 6         16 $result = validate(@$validation_options_ref);
1352             }
1353            
1354 6 100       9 if ($correction_done) {
1355             # --- update arg vector by new value $result ---
1356 5 50       11 if ($value_pos >= 0){
1357 5 100       8 unless ($validation_options_copied) {
1358 4         10 my @corrected_validation_options = @$validation_options_ref;
1359 4         5 $validation_options_ref = \@corrected_validation_options;
1360             }
1361 5         9 $validation_options_ref->[$value_pos] = $result;
1362             }
1363             }
1364             else {
1365 1 50       11 my $print_result = defined ($result) ? "'$result'" : '';
1366 1         8 return $result;
1367             }
1368             }
1369              
1370 7         14 return validate(@$validation_options_ref);
1371             }
1372              
1373             # --- don't name key twice, deletes validated values out of hash -------------------------
1374             # _do_named_parameter
1375             sub _do_named_parameter {
1376 90     90   91 my $first_arg = shift;
1377 90         98 my $hash_ref;
1378            
1379             my $msg_ref;
1380 0         0 my $key;
1381 0         0 my $option_args_ref;
1382              
1383 90         107 my $args_ref = \@_;
1384              
1385 90         85 $call_info = '';
1386              
1387 90 100       132 unless (_p_is_valid(key => Scalar => $first_arg)) {
1388 2         4 $args_ref = _p_validate (validation_args => ArrayRef => $first_arg);
1389 2         3 $key = shift @$args_ref;
1390 2         3 $option_args_ref = shift;
1391             }
1392             else {
1393 88         89 $key = $first_arg;
1394             }
1395              
1396 90         132 $key = _p_validate (key => Scalar => $key);
1397              
1398 90         114 $hash_ref = pop @$args_ref;
1399            
1400 90 100       122 unless (_p_is_valid(option_ref => HashRef => $hash_ref)) {
1401 27         40 $msg_ref = _p_validate (message_ref => CodeRef => $hash_ref);
1402 27         48 $hash_ref = _p_validate (option_ref => HashRef => pop @$args_ref);
1403             }
1404              
1405 90         135 my $value = delete $hash_ref->{$key};
1406              
1407 90 100       168 unless (defined $value) {
1408 55 50       87 if ($option_args_ref) {
1409 0         0 $value = $option_args_ref->{-default};
1410 0         0 print "used default $key => '$value'\n";
1411             # print $option_args_ref->{-description}."\n";
1412             }
1413             }
1414              
1415 90         152 return _p_validate ($key, @$args_ref, $value, $msg_ref);
1416             }
1417              
1418             # --- return value if valid ---------------
1419             # --- call $fail_action if invalid ---------------
1420              
1421             sub _do_validate_parameter {
1422 70     70   75 $call_info = 'parameter ';
1423 70         119 goto &_do_validate;
1424             }
1425              
1426             sub _do_validate_call {
1427 820     820   2236 $call_info = '';
1428 820         1305 goto &_do_validate;
1429             }
1430              
1431             sub _do_validate {
1432 890 100   890   1459 if ($off) {
1433 6         7 my $value_pos = 2;
1434 6         7 my $special_rule = $special_rules->{$_[1]};
1435 6 100       12 $value_pos = $special_rule->{-value_position} if $special_rule;
1436 6 50       18 return $_[$value_pos] if $value_pos >= 0;
1437             }
1438              
1439 884   100     1385 my $subject_info = shift || '';
1440 884         729 my $rule_info = shift;
1441              
1442 884 50       1242 unless ($rule_info) {
1443 0         0 $trouble_level++;
1444 0         0 $fail_action->("rule for validation not set");
1445 0         0 return $_; # in case of fail action doesn't die
1446             }
1447              
1448 884         1088 my $rule_ref = $rule_store->{$rule_info};
1449              
1450 884 100       1280 unless ($rule_ref) {
1451 237         491 my $special_rule = $special_rules->{$rule_info}->{-code};
1452              
1453 237 100       538 return $special_rule->($subject_info, @_) if $special_rule;
1454              
1455 51         64 my $ref_type = ref ($rule_info);
1456            
1457 51 100       128 unless ($ref_type) {
    100          
    50          
1458 2         4 $trouble_level++;
1459 2         9 $fail_action->("unknown rule '$rule_info' for validation");
1460 0         0 return shift; # in case of fail action doesn't die
1461             }
1462             elsif ($ref_type eq 'HASH') { # given rule
1463 34         50 $rule_ref = $rule_info;
1464             # TODO: _p_validate rule ...
1465             }
1466             elsif ($ref_type eq 'CODE') { # where condition for rule
1467             $rule_ref = {
1468             -where => $rule_info,
1469 2     2   6 -message => sub { "$_ does not match free defined rule" },
1470 15         72 };
1471             }
1472             else {
1473 0         0 $trouble_level++;
1474 0         0 $fail_action->("Rules: cannot handle ref type '$ref_type' of rule '$rule_info' for validation");
1475 0         0 return shift; # in case of fail action doesn't die
1476             }
1477             }
1478              
1479 696         714 local $_ = shift;
1480 696         604 my $message_ref = shift;
1481              
1482 696         534 my $orig_value = $_;
1483 696   66     2065 my $test_message_ref = $message_ref || $rule_ref->{-message};
1484              
1485 696 100       1309 my $parent_is_valid = defined $rule_ref->{-as}
1486             ? _check_parent_rules($rule_ref->{-as}, $_)
1487             : 1;
1488              
1489 695 100 100     1639 unless ($parent_is_valid && $rule_ref->{-where}->()) {
1490 105 100       302 $_ = defined ($_) ? "'$_'" : '';
1491 105         158 my $message = $get_caller_info->()."($call_info$subject_info): ".$test_message_ref->();
1492 105 100       208 push (@$message_store, $message) if $message_store;
1493 105         84 $trouble_level++;
1494 105         149 my $result = $fail_action->($message);
1495 64 100       184 return $result if defined $result;
1496             }
1497              
1498 592         1326 return $orig_value;
1499             }
1500              
1501             1;
1502              
1503             __END__