File Coverage

lib/Data/Processor/Validator.pm
Criterion Covered Total %
statement 166 171 97.0
branch 64 74 86.4
condition 32 45 71.1
subroutine 16 16 100.0
pod 0 4 0.0
total 278 310 89.6


line stmt bran cond sub pod time code
1 19     19   240 use 5.10.1;
  19         57  
2 19     19   99 use strict;
  19         28  
  19         383  
3 19     19   83 use warnings;
  19         33  
  19         613  
4              
5             package Data::Processor::Validator;
6              
7 19     19   90 use Carp;
  19         29  
  19         1104  
8 19     19   105 use Scalar::Util qw(blessed);
  19         37  
  19         934  
9              
10 19     19   139 use Data::Processor::Error::Collection;
  19         29  
  19         785  
11 19     19   6311 use Data::Processor::Transformer;
  19         43  
  19         39369  
12              
13              
14             # XXX document this with pod. (if standalone)
15              
16             # Data::Processor::Validator - Validate Data Against a Schema
17              
18             sub new {
19 191     191 0 414 my $class = shift;
20 191         297 my $schema = shift;
21 191         603 my %p = @_;
22             my $self = {
23             schema => $schema // confess ('cannot validate without "schema"'),
24             data => $p{data} // undef,
25             verbose=> $p{verbose} // undef,
26             errors => $p{errors} // Data::Processor::Error::Collection->new(),
27             depth => $p{depth} // 0,
28             indent => $p{indent} // 4,
29 191   33     1991 parent_keys => $p{parent_keys} // ['root'],
      100        
      100        
      66        
      100        
      100        
      100        
30             transformer => Data::Processor::Transformer->new(),
31              
32             };
33 191         387 bless ($self, $class);
34 191         862 return $self;
35             }
36              
37             # (recursively) checks data, or a section thereof,
38             # by instantiating D::P::V objects and calling validate on them
39              
40             sub validate {
41 188     188 0 925 my $self = shift;
42 188         362 $self->{data} = shift;
43 188 100       995 croak ('cannot validate without "data"') unless $self->{data};
44 187         438 $self->{errors} = Data::Processor::Error::Collection->new();
45              
46 187         487 $self->_add_defaults();
47              
48 187         323 for my $key (keys %{$self->{data}}){
  187         474  
49 280         844 $self->explain (">>'$key'");
50              
51             # the shema key is ?
52             # from here we know to have a "twin" key $schema_key in the schema
53 280 100       621 my $schema_key = $self->_schema_twin_key($key) or next;
54              
55             # transformer (transform first)
56 272         651 my $e = $self->{transformer}->transform($key,$schema_key, $self);
57 272 100       476 $self->error($e) if $e;
58              
59             # now validate
60 272         806 $self->__value_is_valid( $key );
61 272         737 $self->__validator_returns_undef($key, $schema_key);
62              
63              
64             # skip if explicitly asked for
65 272 50       587 if ($self->{schema}->{$schema_key}->{no_descend_into}){
66 0         0 $self->explain (
67             ">>skipping '$key' because schema explicitly says so.\n");
68 0         0 next;
69             }
70             # skip data branch if schema key is empty.
71 272 100       328 if (! %{$self->{schema}->{$schema_key}}){
  272         581  
72 7         25 $self->explain (">>skipping '$key' because schema key is empty\n'");
73 7         15 next;
74             }
75 265 100       607 if (! $self->{schema}->{$schema_key}->{members}){
76 168         450 $self->explain (
77             ">>not descending into '$key'. No members specified\n"
78             );
79 168         324 next;
80             }
81              
82             # recursion if we reach this point.
83 97         339 $self->explain (">>descending into '$key'\n");
84              
85 97 100 66     385 if (ref $self->{data}->{$key} eq ref {} ){
    100          
86 92         282 $self->explain
87             (">>'$key' is not a leaf and we descend into it\n");
88             my $e = Data::Processor::Validator->new(
89             $self->{schema}->{$schema_key}->{members},
90 92         345 parent_keys => [@{$self->{parent_keys}}, $key],
91             depth => $self->{depth}+1,
92             verbose => $self->{verbose},
93              
94 92         202 ) ->validate($self->{data}->{$key});
95 92         594 $self->{errors}->add_collection($e);
96              
97             }
98             elsif ((ref $self->{data}->{$key} eq ref [])
99             && $self->{schema}->{$schema_key}->{array}){
100              
101 2         8 $self->explain(
102             ">>'$key' is an array reference so we check all elements\n");
103 2         3 for my $member (@{$self->{data}->{$key}}){
  2         7  
104             next if !defined $member
105 5 50 66     17 && $self->{schema}->{$schema_key}->{allow_empty};
106              
107             my $e = Data::Processor::Validator->new(
108             $self->{schema}->{$schema_key}->{members},
109 4         33 parent_keys => [@{$self->{parent_keys}}, $key],
110             depth => $self->{depth}+1,
111             verbose => $self->{verbose},
112              
113 4         8 ) ->validate($member);
114 4         29 $self->{errors}->add_collection($e);
115             }
116             }
117             # Make sure that key in data is a leaf in schema.
118             # We cannot descend into a non-existing branch in data
119             # but it might be required by the schema.
120             else {
121 3         12 $self->explain(">>checking data key '$key' which is a leaf..");
122 3 50       8 if ($self->{schema}->{$schema_key}->{members}){
123 3         19 $self->explain("but schema requires members.\n");
124 3         13 $self->error("'$key' should have members");
125             }
126             else {
127 0         0 $self->explain("schema key is also a leaf. ok.\n");
128             }
129             }
130             }
131             # look for missing non-optional keys in schema
132             # this is only done on this level.
133             # Otherwise "mandatory" inherited "upwards".
134 187         480 $self->_check_mandatory_keys();
135 187         584 return $self->{errors};
136             }
137              
138             #################
139             # internal methods
140             #################
141              
142             # add an error
143             sub error {
144 39     39 0 62 my $self = shift;
145 39         71 my $string = shift;
146             $self->{errors}->add(
147             message => $string,
148             path => $self->{parent_keys},
149 39         138 );
150             }
151              
152             # explains what we are doing.
153             sub explain {
154 3794     3794 0 4356 my $self = shift;
155 3794         4162 my $string = shift;
156 3794         5539 my $indent = ' ' x ($self->{depth}*$self->{indent});
157 3794         6925 $string =~ s/>>/$indent/;
158 3794 100       7477 print $string if $self->{verbose};
159             }
160              
161              
162             # add defaults. Go over all keys *on that level* and if there is not
163             # a value (or, most oftenly, a key) in data, add the key and the
164             # default value.
165              
166             sub _add_defaults{
167 187     187   243 my $self = shift;
168              
169 187         232 for my $key (keys %{$self->{schema}}){
  187         678  
170 1244 100       2502 next unless $self->{schema}->{$key}->{default};
171             $self->{data}->{$key} = $self->{schema}->{$key}->{default}
172 1 50       5 unless $self->{data}->{$key};
173             }
174             }
175              
176             # check mandatory: look for mandatory fields in all hashes 1 level
177             # below current level (in schema)
178             # for each check if $data has a key.
179             sub _check_mandatory_keys{
180 187     187   289 my $self = shift;
181              
182 187         212 for my $key (keys %{$self->{schema}}){
  187         563  
183 1244         2803 $self->explain(">>Checking if '$key' is mandatory: ");
184 1244 100 66     3533 unless ($self->{schema}->{$key}->{optional}
185             and $self->{schema}->{$key}->{optional}){
186              
187 81         182 $self->explain("true\n");
188 81 100       197 next if defined $self->{data}->{$key};
189              
190             # regex-keys never directly occur.
191 20 100       53 if ($self->{schema}->{$key}->{regex}){
192 10         22 $self->explain(">>regex enabled key found. ");
193 10         43 $self->explain("Checking data keys.. ");
194 10         17 my $c = 0;
195             # look which keys match the regex
196 10         16 for my $c_key (keys %{$self->{data}}){
  10         26  
197 19 100       154 $c++ if $c_key =~ /$key/;
198             }
199 10         43 $self->explain("$c matching occurencies found\n");
200 10 100       45 next if $c > 0;
201             }
202              
203             # should only get here in case of error.
204 11         19 my $error_msg = '';
205             $error_msg = $self->{schema}->{$key}->{error_msg}
206 11 100       42 if $self->{schema}->{$key}->{error_msg};
207              
208 11         18 my $error_clause = '';
209 11 100       31 if( $error_msg ){
210 4         11 $error_clause = " Error msg: '$error_msg'";
211             }
212              
213 11         44 $self->error("mandatory key '$key' missing.".$error_clause);
214             }
215             else{
216 1163         1694 $self->explain("false\n");
217             }
218             }
219             }
220              
221             # find key to validate (section of) data against
222             sub _schema_twin_key{
223 280     280   594 my $self = shift;
224 280         330 my $key = shift;
225              
226 280         331 my $schema_key;
227              
228             # direct match: exact declaration
229 280 100       602 if ($self->{schema}->{$key}){
230 181         347 $self->explain(" ok\n");
231 181         235 $schema_key = $key;
232             }
233             # match against a pattern
234             else {
235 99         136 my $match;
236 99         126 for my $match_key (keys %{$self->{schema}}){
  99         256  
237              
238             # only try to match a key if it has the property
239             # _regex_ set
240             next unless exists $self->{schema}->{$match_key}
241 139 100 66     545 and $self->{schema}->{$match_key}->{regex};
242              
243 97 100       812 if ($key =~ /$match_key/){
244 91         335 $self->explain("'$key' matches $match_key\n");
245 91         182 $schema_key = $match_key;
246             }
247             }
248             }
249              
250             # if $schema_key is still undef we were unable to
251             # match it against a key in the schema.
252 280 100       497 unless ($schema_key){
253 8         43 $self->explain(">>$key not in schema, keys available: ");
254 8         16 $self->explain(join (", ", (keys %{$self->{schema}})));
  8         37  
255 8         24 $self->explain("\n");
256 8         26 $self->error("key '$key' not found in schema\n");
257             }
258 280         621 return $schema_key
259             }
260              
261             # 'validator' specified gets this called to call the callback :-)
262             sub __validator_returns_undef {
263 272     272   338 my $self = shift;
264 272         325 my $key = shift;
265 272         324 my $schema_key = shift;
266 272 100       645 return unless $self->{schema}->{$schema_key}->{validator};
267 137   50     631 $self->explain("running validator for '$key': ".($self->{data}->{$key} // '(undefined)').": \n");
268              
269 137 100 100     508 if (ref $self->{data}->{$key} eq ref []
270             && $self->{schema}->{$schema_key}->{array}){
271              
272 3         5 my $counter = 0;
273 3         5 for my $elem (@{$self->{data}{$key}}){
  3         8  
274             next if !defined $elem
275 7 0 33     16 && $self->{schema}{$schema_key}{allow_empty};
276              
277 7         20 my $return_value = $self->{schema}{$schema_key}{validator}($elem, $self->{data});
278 7 100       46 if ($return_value){
279 3         21 $self->explain("validator error: $return_value (element $counter)\n");
280 3         11 $self->error("Execution of validator for '$key' element $counter returns with error: $return_value");
281             }
282             else {
283 4         12 $self->explain("successful validation for key '$key' element $counter\n");
284             }
285 7         25 $counter++;
286             }
287             }
288             else {
289 134         220 my $validator = $self->{schema}->{$schema_key}->{validator};
290 134         163 my $return_value;
291 134 100       358 if (defined blessed $validator){
292 6         20 $return_value = $validator->validate($self->{data}{$key});
293             }
294             else {
295 128         380 $return_value = $validator->($self->{data}->{$key}, $self->{data});
296             }
297 134 100       8793 if ($return_value){
298 11         46 $self->explain("validator error: $return_value\n");
299 11         61 $self->error("Execution of validator for '$key' returns with error: $return_value");
300             }
301             else {
302 123         313 $self->explain("successful validation for key '$key'\n");
303             }
304             }
305             }
306              
307             # called by validate to check if a value is in line with definitions
308             # in the schema.
309             sub __value_is_valid{
310 272     272   329 my $self = shift;
311 272         338 my $key = shift;
312              
313 272 100 100     983 if (exists $self->{schema}->{$key}
314             and $self->{schema}->{$key}->{value}){
315 14         56 $self->explain('>>'.ref($self->{schema}->{$key}->{value})."\n");
316              
317             # currently, 2 type of restrictions are supported:
318             # (callback) code and regex
319 14 50       70 if (ref($self->{schema}->{$key}->{value}) eq 'CODE'){
    50          
320             # possibly never implement this because of new "validator"
321             }
322             elsif (ref($self->{schema}->{$key}->{value}) eq 'Regexp'){
323 14 100 66     71 if (ref $self->{data}->{$key} eq ref []
324             && $self->{schema}{$key}{array}){
325              
326 1         3 for my $elem (@{$self->{data}{$key}}){
  1         46  
327             next if !defined $elem
328 5 0 33     16 && $self->{schema}{$key}{allow_empty};
329              
330 5         24 $self->explain(">>match '$elem' against '$self->{schema}->{$key}->{value}'");
331              
332 5 100       46 if ($elem =~ m/^$self->{schema}{$key}{value}$/){
333 4         8 $self->explain(" ok.\n");
334             }
335             else{
336             # XXX never reach this?
337 1         4 $self->explain(" no.\n");
338 1         5 $self->error("$elem does not match ^$self->{schema}->{$key}->{value}\$");
339             }
340             }
341             }
342             # XXX this was introduced to support arrays.
343             else {
344 13         68 $self->explain(">>match '$self->{data}->{$key}' against '$self->{schema}->{$key}->{value}'");
345              
346 13 100       239 if ($self->{data}->{$key} =~ m/^$self->{schema}->{$key}->{value}$/){
347 12         42 $self->explain(" ok.\n");
348             }
349             else{
350             # XXX never reach this?
351 1         4 $self->explain(" no.\n");
352 1         5 $self->error("$self->{data}->{$key} does not match ^$self->{schema}->{$key}->{value}\$");
353             }
354             }
355             }
356             else{
357             # XXX match literally? How much sense does this make?!
358             # also, this is not tested
359              
360 0           $self->explain("neither CODE nor Regexp\n");
361 0           $self->error("'$key' not CODE nor Regexp");
362             }
363              
364             }
365             }
366              
367             1;