File Coverage

lib/Data/Processor/Validator.pm
Criterion Covered Total %
statement 176 183 96.1
branch 67 80 83.7
condition 34 51 66.6
subroutine 17 17 100.0
pod 0 4 0.0
total 294 335 87.7


line stmt bran cond sub pod time code
1 19     19   264 use 5.10.1;
  19         76  
2 19     19   108 use strict;
  19         29  
  19         402  
3 19     19   84 use warnings;
  19         34  
  19         662  
4              
5             package Data::Processor::Validator;
6              
7 19     19   91 use Carp;
  19         29  
  19         1204  
8 19     19   105 use Scalar::Util qw(blessed);
  19         70  
  19         850  
9              
10 19     19   135 use Data::Processor::Error::Collection;
  19         33  
  19         1030  
11 19     19   6907 use Data::Processor::Transformer;
  19         45  
  19         46216  
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 439 my $class = shift;
20 191         237 my $schema = shift;
21 191         596 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     2007 parent_keys => $p{parent_keys} // ['root'],
      100        
      100        
      66        
      100        
      100        
      100        
30             transformer => Data::Processor::Transformer->new(),
31              
32             };
33 191         396 bless ($self, $class);
34 191         1017 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 1007 my $self = shift;
42 188         388 $self->{data} = shift;
43 188 100       793 croak ('cannot validate without "data"') unless $self->{data};
44 187         469 $self->{errors} = Data::Processor::Error::Collection->new();
45             # caching 'schema_key' to avoid running _schema_twin_key multiple
46             # times since errors will be added to the collection
47             $self->{schema_keys} = {
48 187         282 map { $_ => $self->_schema_twin_key($_) } keys %{$self->{data}}
  279         595  
  187         647  
49             };
50              
51 187         527 $self->_add_defaults_and_transform();
52              
53             my $order = sub {
54 117     117   219 my ($a, $b) = @_;
55              
56             return 1 if !$self->{schema_keys}->{$a}
57 117 50 66     683 || !$self->{schema}->{$self->{schema_keys}->{$a}}->{order};
58             return -1 if !$self->{schema_keys}->{$b}
59 0 0 0     0 || !$self->{schema}->{$self->{schema_keys}->{$b}}->{order};
60             return $self->{schema}->{$self->{schema_keys}->{$a}}->{order}
61 0         0 <=> $self->{schema}->{$self->{schema_keys}->{$b}}->{order};
62 187         822 };
63              
64 187         270 for my $key (sort { $order->($a, $b) } keys %{$self->{data}}) {
  117         209  
  187         598  
65 280         896 $self->explain (">>'$key'");
66              
67 280 100       603 my $schema_key = $self->{schema_keys}->{$key} or next;
68              
69             # validate
70 271         605 $self->__value_is_valid( $key );
71 271         668 $self->__validator_returns_undef($key, $schema_key);
72              
73              
74             # skip if explicitly asked for
75 271 50       616 if ($self->{schema}->{$schema_key}->{no_descend_into}){
76 0         0 $self->explain (
77             ">>skipping '$key' because schema explicitly says so.\n");
78 0         0 next;
79             }
80             # skip data branch if schema key is empty.
81 271 100       315 if (! %{$self->{schema}->{$schema_key}}){
  271         562  
82 7         25 $self->explain (">>skipping '$key' because schema key is empty\n'");
83 7         16 next;
84             }
85 264 100       551 if (! $self->{schema}->{$schema_key}->{members}){
86 167         481 $self->explain (
87             ">>not descending into '$key'. No members specified\n"
88             );
89 167         338 next;
90             }
91              
92             # recursion if we reach this point.
93 97         351 $self->explain (">>descending into '$key'\n");
94              
95 97 100 66     392 if (ref $self->{data}->{$key} eq ref {} ){
    100          
96 92         279 $self->explain
97             (">>'$key' is not a leaf and we descend into it\n");
98             my $e = Data::Processor::Validator->new(
99             $self->{schema}->{$schema_key}->{members},
100 92         358 parent_keys => [@{$self->{parent_keys}}, $key],
101             depth => $self->{depth}+1,
102             verbose => $self->{verbose},
103              
104 92         172 ) ->validate($self->{data}->{$key});
105 92         628 $self->{errors}->add_collection($e);
106              
107             }
108             elsif ((ref $self->{data}->{$key} eq ref [])
109             && $self->{schema}->{$schema_key}->{array}){
110              
111 2         9 $self->explain(
112             ">>'$key' is an array reference so we check all elements\n");
113 2         5 for my $member (@{$self->{data}->{$key}}){
  2         9  
114             next if !defined $member
115 5 50 66     18 && $self->{schema}->{$schema_key}->{allow_empty};
116              
117             my $e = Data::Processor::Validator->new(
118             $self->{schema}->{$schema_key}->{members},
119 4         16 parent_keys => [@{$self->{parent_keys}}, $key],
120             depth => $self->{depth}+1,
121             verbose => $self->{verbose},
122              
123 4         8 ) ->validate($member);
124 4         21 $self->{errors}->add_collection($e);
125             }
126             }
127             # Make sure that key in data is a leaf in schema.
128             # We cannot descend into a non-existing branch in data
129             # but it might be required by the schema.
130             else {
131 3         23 $self->explain(">>checking data key '$key' which is a leaf..");
132 3 50       10 if ($self->{schema}->{$schema_key}->{members}){
133 3         8 $self->explain("but schema requires members.\n");
134 3         10 $self->error("'$key' should have members");
135             }
136             else {
137 0         0 $self->explain("schema key is also a leaf. ok.\n");
138             }
139             }
140             }
141             # look for missing non-optional keys in schema
142             # this is only done on this level.
143             # Otherwise "mandatory" inherited "upwards".
144 187         476 $self->_check_mandatory_keys();
145 187         1138 return $self->{errors};
146             }
147              
148             #################
149             # internal methods
150             #################
151              
152             # add an error
153             sub error {
154 39     39 0 70 my $self = shift;
155 39         66 my $string = shift;
156             $self->{errors}->add(
157             message => $string,
158             path => $self->{parent_keys},
159 39         137 );
160             }
161              
162             # explains what we are doing.
163             sub explain {
164 3950     3950 0 4281 my $self = shift;
165 3950         4221 my $string = shift;
166 3950         5588 my $indent = ' ' x ($self->{depth}*$self->{indent});
167 3950         7314 $string =~ s/>>/$indent/;
168 3950 100       7667 print $string if $self->{verbose};
169             }
170              
171              
172             # add defaults and transform. Go over all keys *on that level*
173             # and if there is not a value (or, most oftenly, a key) in data,
174             # add the key and the default value.
175              
176             sub _add_defaults_and_transform {
177 187     187   223 my $self = shift;
178              
179 187         228 for my $key (keys %{$self->{schema}}){
  187         668  
180 1323 100       2290 next unless $self->{schema}->{$key}->{default};
181             $self->{data}->{$key} = $self->{schema}->{$key}->{default}
182 1 50       4 unless $self->{data}->{$key};
183             }
184              
185 187         315 for my $key (keys %{$self->{data}}){
  187         383  
186 280 100       545 my $schema_key = $self->{schema_keys}->{$key} or next;
187              
188             # transformer
189 271         687 my $e = $self->{transformer}->transform($key, $schema_key, $self);
190 271 100       564 $self->error($e) if $e;
191             }
192             }
193              
194             # check mandatory: look for mandatory fields in all hashes 1 level
195             # below current level (in schema)
196             # for each check if $data has a key.
197             sub _check_mandatory_keys{
198 187     187   243 my $self = shift;
199              
200 187         221 for my $key (keys %{$self->{schema}}){
  187         562  
201 1323         2860 $self->explain(">>Checking if '$key' is mandatory: ");
202 1323 100 66     3667 unless ($self->{schema}->{$key}->{optional}
203             and $self->{schema}->{$key}->{optional}){
204              
205 81         164 $self->explain("true\n");
206 81 100       217 next if defined $self->{data}->{$key};
207              
208             # regex-keys never directly occur.
209 20 100       86 if ($self->{schema}->{$key}->{regex}){
210 10         39 $self->explain(">>regex enabled key found. ");
211 10         26 $self->explain("Checking data keys.. ");
212 10         13 my $c = 0;
213             # look which keys match the regex
214 10         15 for my $c_key (keys %{$self->{data}}){
  10         29  
215 19 100       163 $c++ if $c_key =~ /$key/;
216             }
217 10         48 $self->explain("$c matching occurencies found\n");
218 10 100       58 next if $c > 0;
219             }
220              
221             # should only get here in case of error.
222 11         27 my $error_msg = '';
223             $error_msg = $self->{schema}->{$key}->{error_msg}
224 11 100       30 if $self->{schema}->{$key}->{error_msg};
225              
226 11         17 my $error_clause = '';
227 11 100       35 if( $error_msg ){
228 4         11 $error_clause = " Error msg: '$error_msg'";
229             }
230              
231 11         40 $self->error("mandatory key '$key' missing.".$error_clause);
232             }
233             else{
234 1242         1782 $self->explain("false\n");
235             }
236             }
237             }
238              
239             # find key to validate (section of) data against
240             sub _schema_twin_key{
241 279     279   342 my $self = shift;
242 279         331 my $key = shift;
243              
244 279         299 my $schema_key;
245              
246             # direct match: exact declaration
247 279 100       564 if ($self->{schema}->{$key}){
248 180         386 $self->explain(" ok\n");
249 180         301 $schema_key = $key;
250             }
251             # match against a pattern
252             else {
253 99         127 my $match;
254 99         114 for my $match_key (keys %{$self->{schema}}){
  99         267  
255              
256             # only try to match a key if it has the property
257             # _regex_ set
258             next unless exists $self->{schema}->{$match_key}
259 141 100 66     521 and $self->{schema}->{$match_key}->{regex};
260              
261 97 100       840 if ($key =~ /$match_key/){
262 91         368 $self->explain("'$key' matches $match_key\n");
263 91         192 $schema_key = $match_key;
264             }
265             }
266             }
267              
268             # if $schema_key is still undef we were unable to
269             # match it against a key in the schema.
270 279 100       477 unless ($schema_key){
271 8         35 $self->explain(">>$key not in schema, keys available: ");
272 8         15 $self->explain(join (", ", (keys %{$self->{schema}})));
  8         38  
273 8         26 $self->explain("\n");
274 8         29 $self->error("key '$key' not found in schema\n");
275             }
276 279         922 return $schema_key
277             }
278              
279             # 'validator' specified gets this called to call the callback :-)
280             sub __validator_returns_undef {
281 271     271   345 my $self = shift;
282 271         305 my $key = shift;
283 271         307 my $schema_key = shift;
284 271 100       607 return unless $self->{schema}->{$schema_key}->{validator};
285 137   50     680 $self->explain("running validator for '$key': ".($self->{data}->{$key} // '(undefined)').": \n");
286              
287 137 100 100     558 if (ref $self->{data}->{$key} eq ref []
288             && $self->{schema}->{$schema_key}->{array}){
289              
290 3         5 my $counter = 0;
291 3         6 for my $elem (@{$self->{data}{$key}}){
  3         8  
292             next if !defined $elem
293 7 0 33     17 && $self->{schema}{$schema_key}{allow_empty};
294              
295 7         27 my $return_value = $self->{schema}{$schema_key}{validator}($elem, $self->{data});
296 7 100       50 if ($return_value){
297 3         15 $self->explain("validator error: $return_value (element $counter)\n");
298 3         23 $self->error("Execution of validator for '$key' element $counter returns with error: $return_value");
299             }
300             else {
301 4         12 $self->explain("successful validation for key '$key' element $counter\n");
302             }
303 7         14 $counter++;
304             }
305             }
306             else {
307 134         212 my $validator = $self->{schema}->{$schema_key}->{validator};
308 134         152 my $return_value;
309 134 100       387 if (defined blessed $validator){
310 6         22 $return_value = $validator->validate($self->{data}{$key});
311             }
312             else {
313 128         365 $return_value = $validator->($self->{data}->{$key}, $self->{data});
314             }
315 134 100       9430 if ($return_value){
316 11         50 $self->explain("validator error: $return_value\n");
317 11         44 $self->error("Execution of validator for '$key' returns with error: $return_value");
318             }
319             else {
320 123         334 $self->explain("successful validation for key '$key'\n");
321             }
322             }
323             }
324              
325             # called by validate to check if a value is in line with definitions
326             # in the schema.
327             sub __value_is_valid{
328 271     271   342 my $self = shift;
329 271         326 my $key = shift;
330              
331 271 100 100     981 if (exists $self->{schema}->{$key}
332             and $self->{schema}->{$key}->{value}){
333 14         62 $self->explain('>>'.ref($self->{schema}->{$key}->{value})."\n");
334              
335             # currently, 2 type of restrictions are supported:
336             # (callback) code and regex
337 14 50       75 if (ref($self->{schema}->{$key}->{value}) eq 'CODE'){
    50          
338             # possibly never implement this because of new "validator"
339             }
340             elsif (ref($self->{schema}->{$key}->{value}) eq 'Regexp'){
341 14 100 66     97 if (ref $self->{data}->{$key} eq ref []
342             && $self->{schema}{$key}{array}){
343              
344 1         2 for my $elem (@{$self->{data}{$key}}){
  1         4  
345             next if !defined $elem
346 5 0 33     10 && $self->{schema}{$key}{allow_empty};
347              
348 5         21 $self->explain(">>match '$elem' against '$self->{schema}->{$key}->{value}'");
349              
350 5 100       41 if ($elem =~ m/^$self->{schema}{$key}{value}$/){
351 4         9 $self->explain(" ok.\n");
352             }
353             else{
354             # XXX never reach this?
355 1         3 $self->explain(" no.\n");
356 1         13 $self->error("$elem does not match ^$self->{schema}->{$key}->{value}\$");
357             }
358             }
359             }
360             # XXX this was introduced to support arrays.
361             else {
362 13         66 $self->explain(">>match '$self->{data}->{$key}' against '$self->{schema}->{$key}->{value}'");
363              
364 13 100       227 if ($self->{data}->{$key} =~ m/^$self->{schema}->{$key}->{value}$/){
365 12         50 $self->explain(" ok.\n");
366             }
367             else{
368             # XXX never reach this?
369 1         5 $self->explain(" no.\n");
370 1         6 $self->error("$self->{data}->{$key} does not match ^$self->{schema}->{$key}->{value}\$");
371             }
372             }
373             }
374             else{
375             # XXX match literally? How much sense does this make?!
376             # also, this is not tested
377              
378 0           $self->explain("neither CODE nor Regexp\n");
379 0           $self->error("'$key' not CODE nor Regexp");
380             }
381              
382             }
383             }
384              
385             1;