File Coverage

blib/lib/Validator/Declarative.pm
Criterion Covered Total %
statement 118 125 94.4
branch 58 64 90.6
condition 21 24 87.5
subroutine 22 24 91.6
pod 4 4 100.0
total 223 241 92.5


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2 37     37   61287 use strict;
  37         80  
  37         1598  
3 37     37   212 use warnings;
  37         72  
  37         2619  
4              
5             package Validator::Declarative;
6             {
7             $Validator::Declarative::VERSION = '1.20130722.2105';
8             }
9              
10             # ABSTRACT: Declarative parameters validation
11              
12 37     37   42047 use Error qw/ :try /;
  37         180983  
  37         888  
13 37     37   52428 use Module::Load;
  37         87797  
  37         299  
14 37     37   52881 use Readonly;
  37         181822  
  37         91784  
15              
16             Readonly my $RULE_CONSTRAINT => 'constraint';
17             Readonly my $RULE_CONVERTER => 'converter';
18             Readonly my $RULE_TYPE => 'type';
19              
20 1     1 1 1050 sub register_constraint { _register_rules( $RULE_CONSTRAINT => \@_ ) }
21 38     38 1 1421 sub register_converter { _register_rules( $RULE_CONVERTER => \@_ ) }
22 75     75 1 1005 sub register_type { _register_rules( $RULE_TYPE => \@_ ) }
23              
24             sub validate {
25 121     121 1 31334 my $params = shift;
26 121         203 my $definitions = shift;
27              
28 121 100       579 throw Error::Simple('invalid "params"')
29             if ref($params) ne 'ARRAY';
30              
31 120 100       509 throw Error::Simple('invalid rules definitions')
32             if ref($definitions) ne 'ARRAY';
33              
34 119 100       473 throw Error::Simple('count of params does not match count of rules definitions')
35             if scalar(@$params) * 2 != scalar(@$definitions);
36              
37 118 100       350 throw Error::Simple('extra parameters')
38             if @_;
39              
40             # one-level copy to not harm input parameters
41 117         22439 $params = [@$params];
42 117         80862 $definitions = [@$definitions];
43              
44 117         235 my ( @errors, @output );
45 117         377 while (@$params) {
46 143324         2340865 my $value = shift @$params;
47 143324         246255 my $name = shift @$definitions;
48 143324         212634 my $rules = shift @$definitions;
49              
50             try {
51 143324     143324   3067493 my $normalized_rules = _normalize_rules($rules);
52 143320         345383 my $is_optional = _check_constraints( $value, $normalized_rules->{constraints} );
53 143316         326665 $value = _run_convertors( $value, $normalized_rules->{converters}, $is_optional );
54 143315 100 100     782731 _check_types( $value, $normalized_rules->{types} ) if defined($value) && !$is_optional;
55 140141         653446 push @output, $value;
56             }
57             catch Error with {
58 3183     3183   126432 my $error = shift;
59 3183         4925 my $stringified_value = $value;
60 3183 100       6707 $stringified_value = '' if !defined($stringified_value);
61 3183 100       9916 $stringified_value = 'empty string' if $stringified_value eq '';
62 3183         8069 $stringified_value =~ s/[^[:print:]]/./g;
63 3183         18552 push @errors, sprintf( '%s: %s %s', $name, $stringified_value, $error->{-text} );
64 3183         9296 push @output, undef;
65 143324         946617 };
66             }
67              
68 117 100       3760 throw Error::Simple( join "\n" => @errors ) if @errors;
69              
70 75         78851 return @output;
71             }
72              
73             #
74             # INTERNALS
75             #
76             my $registered_rules = {};
77             my $registered_name_to_kind = {};
78              
79             # stubs for successful/unsuccesfull validations
80 0     0   0 sub _validate_pass { my ($input) = @_; return $input; }
  0         0  
81 0     0   0 sub _validate_fail { throw Error::Simple('failed permanently'); }
82              
83             sub _normalize_rules {
84 143324     143324   225572 my $rules = shift;
85              
86 143324         191849 my $types = {};
87 143324         202584 my $converters = {};
88 143324         181406 my $constraints = {};
89              
90             # if there were only one rule - normalize to arrayref anyway
91 143324 100       500674 $rules = [$rules] if ref($rules) ne 'ARRAY';
92              
93             #
94             # each rule can be string (name of simple rule) or arrayref/hashref (parametrized rule)
95             # let's normalize them - convert everything to arrayrefs
96             # hashrefs should not contain more than one key/value pair
97             #
98             # validator parameters (all items in resulting arrayref, except first one) should
99             # left intact, i.e. if parameter was scalar, it should be saved as scalar,
100             # arrayref as arrayref, and so on - there is no place for any conversion
101             #
102 143324         256104 foreach my $rule (@$rules) {
103 143360         155163 my $result;
104              
105 143360 100       427919 if ( ref($rule) eq 'ARRAY' ) {
    100          
    100          
106 1         2 $result = $rule;
107             }
108             elsif ( ref($rule) eq 'HASH' ) {
109 213 100       611 throw Error::Simple('hashref rule should have exactly one key/value pair')
110             if keys %$rule > 1;
111 212         781 $result = [%$rule];
112             }
113             elsif ( ref($rule) ) {
114 1         11 throw Error::Simple( 'rule definition can\'t be reference to ' . ref($rule) );
115             }
116             else {
117             ## should be plain string, so this is name of simple rule
118 143145         300714 $result = [$rule];
119             }
120              
121 143358         237793 my $name = $result->[0];
122 143358         183231 my $params = $result->[1];
123              
124 143358 100       329429 throw Error::Simple("rule $name is not registered")
125             if !exists $registered_name_to_kind->{$name};
126              
127 143357 100       284809 throw Error::Simple("rule $name can accept not more than one parameter")
128             if @$result > 2;
129              
130 143356         209758 my $rule_kind = $registered_name_to_kind->{$name};
131              
132 143356 100       447212 if ( $rule_kind eq $RULE_TYPE ) {
    100          
    50          
133 143267         1210232 $types->{$name} = $params;
134             }
135             elsif ( $rule_kind eq $RULE_CONVERTER ) {
136 50         591 $converters->{$name} = $params;
137             }
138             elsif ( $rule_kind eq $RULE_CONSTRAINT ) {
139 39         1169 $constraints->{$name} = $params;
140             }
141             else {
142             ## we should never pass here
143 0         0 die("internal error: rule $name is registered as $rule_kind");
144             }
145             }
146              
147             return {
148 143320         600115 types => $types,
149             converters => $converters,
150             constraints => $constraints,
151             };
152             }
153              
154             sub _check_constraints {
155 143320     143320   208115 my ( $value, $constraints ) = @_;
156              
157             # check for built-in constraints (required/optional/not_empty)
158 143320         210836 my $is_required = exists $constraints->{required};
159 143320         164732 my $is_optional = exists $constraints->{optional};
160              
161 143320 100 100     356638 throw Error::Simple('both required and optional are specified')
162             if $is_required && $is_optional;
163              
164 143319 100       224667 if ($is_optional) {
165 36         127 delete $constraints->{optional};
166             ## there is nothing else to do
167             }
168             else {
169 143283         172803 delete $constraints->{required};
170              
171 143283 100       283639 throw Error::Simple('parameter is required')
172             if !defined($value);
173              
174             # check for all non-built-in constraints
175 143280         505140 while ( my ( $rule_name, $rule_params ) = each %$constraints ) {
176 0         0 my $code = $registered_rules->{$RULE_CONSTRAINT}{$rule_name};
177 0         0 $code->( $value, $rule_params );
178             }
179             }
180              
181 143316         370805 return $is_optional;
182             }
183              
184             sub _run_convertors {
185 143316     143316   229959 my ( $value, $converters, $is_optional ) = @_;
186              
187             # process "default" converter (if any)
188 143316         190596 my $has_default = exists $converters->{default};
189              
190 143316 100 100     363978 throw Error::Simple('"default" specified without "optional"')
191             if $has_default && !$is_optional;
192              
193 143315 100       280045 if ($has_default) {
194 2 100       7 $value = $converters->{default} if !defined($value);
195 2         4 delete $converters->{default};
196             }
197              
198 143315 50       338250 throw Error::Simple('there is more than one converter, except "default"')
199             if keys %$converters > 1;
200              
201             # process non-"default" converter (if any)
202 143315 100       294068 if (%$converters) {
203 46         82 my ( $rule_name, $rule_params ) = %$converters;
204 46         133 my $code = $registered_rules->{$RULE_CONVERTER}{$rule_name};
205 46         268 $value = $code->( $value, $rule_params );
206             }
207              
208 143315         330563 return $value;
209             }
210              
211             sub _check_types {
212 143279     143279   213355 my ( $value, $types ) = @_;
213              
214             # first successful check wins, all others will not be checked
215 143279 100 100     946704 return if !%$types || exists( $types->{any} ) || exists( $types->{string} );
      100        
216              
217 143231         157402 my $saved_error;
218 143231         477626 while ( my ( $rule_name, $rule_params ) = each %$types ) {
219 143231         148671 my $last_error;
220             try {
221 143231     143231   3180602 my $code = $registered_rules->{$RULE_TYPE}{$rule_name};
222 143231         1009171 $code->( $value, $rule_params );
223             }
224             catch Error with {
225 3174     3174   523827 $last_error = $saved_error = shift;
226 143231         882622 };
227 143231 100       2306458 return if !$last_error;
228             }
229              
230 3174 50       52981 if ( scalar keys %$types == 1 ) {
231 3174         26552 throw $saved_error;
232             }
233             else {
234 0         0 throw Error::Simple('does not satisfy any type');
235             }
236             }
237              
238             sub _register_rules {
239 114     114   863 my $kind = shift;
240 114         705 my $rules = shift;
241              
242 114 50 100     347 throw Error::Simple(qq|Can't register rule of kind <$kind>|)
      66        
243             if $kind ne $RULE_TYPE
244             && $kind ne $RULE_CONVERTER
245             && $kind ne $RULE_CONSTRAINT;
246              
247 114         2897 $rules = {@$rules};
248              
249 114         770 while ( my ( $name, $code ) = each %$rules ) {
250              
251 1230 50 33     4277 throw Error::Simple(qq|Can't register rule without name|)
252             if !defined($name) || !length($name);
253              
254 1230 50       2581 throw Error::Simple(qq|Rule <$name> already registered|)
255             if exists( $registered_name_to_kind->{$name} );
256              
257 1230         2405 $registered_rules->{$kind}{$name} = $code;
258 1230         5869 $registered_name_to_kind->{$name} = $kind;
259             }
260             }
261              
262             sub _register_default_constraints {
263             ## built-in constraints implemented inline
264 37     37   294 $registered_name_to_kind->{$_} = $RULE_CONSTRAINT for qw/ required optional not_empty /;
265             }
266              
267             sub _register_default_converters {
268             ## built-in converters implemented inline
269 37     37   222 $registered_name_to_kind->{$_} = $RULE_CONVERTER for qw/ default /;
270             }
271              
272             sub _register_default_types {
273             ## built-in types implemented inline
274 37     37   344 $registered_name_to_kind->{$_} = $RULE_TYPE for qw/ any string /;
275             }
276              
277             sub _load_base_rules {
278 37     37   100 for my $plugin (qw/ SimpleType ParametrizedType Converters /) {
279 111         1315 my $module = __PACKAGE__ . '::Rules::' . $plugin;
280 111         479 load $module;
281             }
282             }
283              
284             _register_default_constraints();
285             _register_default_converters();
286             _register_default_types();
287             _load_base_rules();
288              
289              
290             1; # End of Validator::Declarative
291              
292              
293             __END__