File Coverage

blib/lib/Method/ParamValidator.pm
Criterion Covered Total %
statement 98 119 82.3
branch 24 46 52.1
condition 3 3 100.0
subroutine 21 23 91.3
pod 6 7 85.7
total 152 198 76.7


line stmt bran cond sub pod time code
1             package Method::ParamValidator;
2              
3             $Method::ParamValidator::VERSION = '0.14';
4             $Method::ParamValidator::AUTHORITY = 'cpan:MANWAR';
5              
6             =head1 NAME
7              
8             Method::ParamValidator - Configurable method parameter validator.
9              
10             =head1 VERSION
11              
12             Version 0.14
13              
14             =cut
15              
16 6     6   249730 use 5.006;
  6         53  
17 6     6   3111 use JSON;
  6         62663  
  6         32  
18 6     6   3846 use Data::Dumper;
  6         35327  
  6         425  
19              
20 6     6   2435 use Method::ParamValidator::Key::Field;
  6         23  
  6         238  
21 6     6   2543 use Method::ParamValidator::Key::Method;
  6         22  
  6         305  
22 6     6   3412 use Method::ParamValidator::Exception::InvalidMethodName;
  6         21  
  6         224  
23 6     6   2500 use Method::ParamValidator::Exception::MissingParameters;
  6         20  
  6         195  
24 6     6   2648 use Method::ParamValidator::Exception::InvalidParameterDataStructure;
  6         18  
  6         183  
25 6     6   2481 use Method::ParamValidator::Exception::MissingRequiredParameter;
  6         16  
  6         183  
26 6     6   2477 use Method::ParamValidator::Exception::MissingMethodName;
  6         16  
  6         185  
27 6     6   2421 use Method::ParamValidator::Exception::MissingFieldName;
  6         18  
  6         183  
28 6     6   2452 use Method::ParamValidator::Exception::UndefinedRequiredParameter;
  6         17  
  6         182  
29 6     6   2720 use Method::ParamValidator::Exception::FailedParameterCheckConstraint;
  6         15  
  6         163  
30              
31 6     6   35 use Moo;
  6         10  
  6         20  
32 6     6   1714 use namespace::autoclean;
  6         12  
  6         24  
33              
34             has [ qw(fields methods) ] => (is => 'rw');
35             has 'config' => (is => 'ro', predicate => 1);
36              
37             =head1 DESCRIPTION
38              
39             It provides easy way to configure and validate method parameters. It is going to
40             help configure and validate all my packages WWW::Google::*.It is just a prototype
41             as of now but will be extended as per the requirements.
42              
43             =head1 SYNOPSIS
44              
45             =head2 Setting up method validator manually.
46              
47             use strict; use warnings;
48             use Test::More;
49             use Method::ParamValidator;
50              
51             my $validator = Method::ParamValidator->new;
52             $validator->add_field({ name => 'firstname', format => 's' });
53             $validator->add_field({ name => 'lastname', format => 's' });
54             $validator->add_field({ name => 'age', format => 'd' });
55             $validator->add_field({ name => 'sex', format => 's' });
56              
57             $validator->add_method({ name => 'add_user',
58             fields => { firstname => 1, lastname => 1, age => 1, sex => 0 }});
59              
60             throws_ok { $validator->validate('get_xyz') } qr/Invalid method name received/;
61             throws_ok { $validator->validate('add_user') } qr/Missing parameters/;
62             throws_ok { $validator->validate('add_user', []) } qr/Invalid parameters data structure/;
63             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 'A' }) } qr/Parameter failed check constraint/;
64             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 10, sex => 's' }) } qr/Parameter failed check constraint/;
65             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L' }) } qr/Missing required parameter/;
66             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => undef, age => 10 }) } qr/Undefined required parameter/;
67             throws_ok { $validator->validate('add_user', { firstname => 'F' }) } qr/Missing required parameter/;
68             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 40, location => 'X' }) } qr/Parameter failed check constraint/;
69             lives_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 40, location => 'UK' }) };
70             lives_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 40, location => 'uk' }) };
71              
72             done_testing();
73              
74             =head2 Setting up method validator using configuration file.
75              
76             Sample configuration file in C format.
77              
78             { "fields" : [ { "name" : "firstname", "format" : "s" },
79             { "name" : "lastname", "format" : "s" },
80             { "name" : "age", "format" : "d" },
81             { "name" : "sex", "format" : "s" }
82             ],
83             "methods" : [ { "name" : "add_user",
84             "fields": { "firstname" : "1",
85             "lastname" : "1",
86             "age" : "1",
87             "sex" : "0"
88             }
89             }
90             ]
91             }
92              
93             Then you just need one line to get everything setup using the above configuration file C.
94              
95             use strict; use warnings;
96             use Test::More;
97             use Test::Exception;
98             use Method::ParamValidator;
99              
100             my $validator = Method::ParamValidator->new({ config => "config.json" });
101              
102             throws_ok { $validator->validate('get_xyz') } qr/Invalid method name received/;
103             throws_ok { $validator->validate('add_user') } qr/Missing parameters/;
104             throws_ok { $validator->validate('add_user', []) } qr/Invalid parameters data structure/;
105             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 'A' }) } qr/Parameter failed check constraint/;
106             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 10, sex => 's' }) } qr/Parameter failed check constraint/;
107             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L' }) } qr/Missing required parameter/;
108             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => undef, age => 10 }) } qr/Undefined required parameter/;
109             throws_ok { $validator->validate('add_user', { firstname => 'F' }) } qr/Missing required parameter/;
110             throws_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 40, location => 'X' }) } qr/Parameter failed check constraint/;
111             lives_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 40, location => 'UK' }) };
112             lives_ok { $validator->validate('add_user', { firstname => 'F', lastname => 'L', age => 40, location => 'uk' }) };
113              
114             done_testing();
115              
116             =head2 Hooking your own check method
117              
118             It allows you to provide your own method for validating a field as shown below:
119              
120             use strict; use warnings;
121             use Test::More;
122             use Test::Exception;
123             use Method::ParamValidator;
124              
125             my $validator = Method::ParamValidator->new;
126              
127             my $LOCATION = { 'USA' => 1, 'UK' => 1 };
128             sub lookup { exists $LOCATION->{uc($_[0])} };
129              
130             $validator->add_field({ name => 'location', format => 's', check => \&lookup });
131             $validator->add_method({ name => 'check_location', fields => { location => 1 }});
132              
133             throws_ok { $validator->validate('check_location', { location => 'X' }) } qr/Parameter failed check constraint/;
134              
135             done_testing();
136              
137             The above can be achieved using the configuration file as shown below:
138              
139             { "fields" : [
140             { "name" : "location", "format" : "s", "source": [ "USA", "UK" ] }
141             ],
142             "methods" : [
143             { "name" : "check_location", "fields": { "location" : "1" } }
144             ]
145             }
146              
147             Using the above configuration file test the code as below:
148              
149             use strict; use warnings;
150             use Test::More;
151             use Test::Exception;
152             use Method::ParamValidator;
153              
154             my $validator = Method::ParamValidator->new({ config => "config.json" });
155              
156             throws_ok { $validator->validate('check_location', { location => 'X' }) } qr/Parameter failed check constraint/;
157              
158             done_testing();
159              
160             =head2 Plug-n-Play with Moo package
161              
162             Lets start with a basic Moo package C.
163              
164             package Calculator;
165              
166             use Moo;
167             use namespace::autoclean;
168              
169             sub do {
170             my ($self, $param) = @_;
171              
172             if ($param->{op} eq 'add') {
173             return ($param->{a} + $param->{b});
174             }
175             elsif ($param->{op} eq 'sub') {
176             return ($param->{a} - $param->{b});
177             }
178             elsif ($param->{op} eq 'mul') {
179             return ($param->{a} * $param->{b});
180             }
181             }
182              
183             1;
184              
185             Now we need to create configuration file for the package C as below:
186              
187             { "fields" : [ { "name" : "op", "format" : "s", "source": [ "add", "sub", "mul" ] },
188             { "name" : "a", "format" : "d" },
189             { "name" : "b", "format" : "d" }
190             ],
191             "methods" : [ { "name" : "do",
192             "fields": { "op" : "1",
193             "a" : "1",
194             "b" : "1"
195             }
196             }
197             ]
198             }
199              
200             Finally plug the validator to the package C as below:
201              
202             use Method::ParamValidator;
203              
204             has 'validator' => (
205             is => 'ro',
206             default => sub { Method::ParamValidator->new(config => "config.json") }
207             );
208              
209             before [qw/do/] => sub {
210             my ($self, $param) = @_;
211              
212             my $method = (caller(1))[3];
213             $method =~ /(.*)\:\:(.*)$/;
214             $self->validator->validate($2, $param);
215             };
216              
217             Here is unit test for the package C.
218              
219             use strict; use warnings;
220             use Test::More;
221             use Test::Exception;
222             use Calculator;
223              
224             my $calc = Calculator->new;
225              
226             is($calc->do({ op => 'add', a => 4, b => 2 }), 6);
227             is($calc->do({ op => 'sub', a => 4, b => 2 }), 2);
228             is($calc->do({ op => 'mul', a => 4, b => 2 }), 8);
229              
230             throws_ok { $calc->do({ op => 'add' }) } qr/Missing required parameter. \(a\)/;
231             throws_ok { $calc->do({ op => 'add', a => 1 }) } qr/Missing required parameter. \(b\)/;
232             throws_ok { $calc->do({ op => 'x', a => 1, b => 2 }) } qr/Parameter failed check constraint. \(op\)/;
233             throws_ok { $calc->do({ op => 'add', a => 'x', b => 2 }) } qr/Parameter failed check constraint. \(a\)/;
234             throws_ok { $calc->do({ op => 'add', a => 1, b => 'x' }) } qr/Parameter failed check constraint. \(b\)/;
235              
236             done_testing();
237              
238             =cut
239              
240             sub BUILD {
241 3     3 0 6740 my ($self) = @_;
242              
243 3 100       25 if ($self->has_config) {
244 1         2 my $data = do {
245 1     1   27 open (my $fh, "<:encoding(utf-8)", $self->config);
  1         6  
  1         1  
  1         7  
246 1         9639 local $/;
247             <$fh>
248 1         26 };
249              
250 1         68 my $config = JSON->new->decode($data);
251              
252 1         6 my ($fields);
253 1         2 foreach (@{$config->{fields}}) {
  1         4  
254 5         3824 my $source = {};
255 5 100       12 if (exists $_->{source}) {
256 2         4 foreach my $v (@{$_->{source}}) {
  2         5  
257 4         9 $source->{uc($v)} = 1;
258             }
259             }
260              
261             $self->add_field({
262             name => $_->{name},
263             format => $_->{format},
264             source => $source,
265             multi => $_->{multi},
266 5         21 });
267             }
268              
269 1         74 foreach my $method (@{$config->{methods}}) {
  1         3  
270 2         1445 $self->add_method($method);
271             }
272             }
273             }
274              
275             =head1 METHODS
276              
277             =head2 validate($method_name, \%params)
278              
279             Validates the given method C<$name> against the given parameters C<\%params>.
280             Throws exception if validation fail.
281              
282             =cut
283              
284             sub validate {
285 21     21 1 9286 my ($self, $key, $values) = @_;
286              
287 21         59 my @caller = caller(0);
288 21 50       683 @caller = caller(2) if $caller[3] eq '(eval)';
289              
290 21 50       48 Method::ParamValidator::Exception::MissingMethodName->throw({
291             method => 'validate',
292             filename => $caller[1],
293             line => $caller[2] }) unless (defined $key);
294              
295             Method::ParamValidator::Exception::InvalidMethodName->throw({
296             method => $key,
297             filename => $caller[1],
298 21 100       78 line => $caller[2] }) unless (exists $self->{methods}->{$key});
299              
300 19 100       56 Method::ParamValidator::Exception::MissingParameters->throw({
301             method => $key,
302             filename => $caller[1],
303             line => $caller[2] }) unless (defined $values);
304              
305 17 100       58 Method::ParamValidator::Exception::InvalidParameterDataStructure->throw({
306             method => $key,
307             filename => $caller[1],
308             line => $caller[2] }) unless (ref($values) eq 'HASH');
309              
310 15         37 my $method = $self->get_method($key);
311 15         54 my $fields = $method->get_fields;
312 15         19 foreach my $field (@{$fields}) {
  15         30  
313 34         74 my $field_name = $field->name;
314 34 100       63 if ($method->is_required_field($field_name)) {
315             Method::ParamValidator::Exception::MissingRequiredParameter->throw({
316             method => $key,
317             field => sprintf("(%s)", $field_name),
318             filename => $caller[1],
319 27 100       106 line => $caller[2] }) unless (exists $values->{$field_name});
320             Method::ParamValidator::Exception::UndefinedRequiredParameter->throw({
321             method => $key,
322             field => sprintf("(%s)", $field_name),
323             filename => $caller[1],
324 22 100       75 line => $caller[2] }) unless (defined $values->{$field_name});
325             }
326              
327             Method::ParamValidator::Exception::FailedParameterCheckConstraint->throw({
328             method => $key,
329             field => sprintf("(%s)", $field_name),
330             filename => $caller[1],
331             line => $caller[2] })
332 26 100 100     92 if (defined $values->{$field_name} && !$field->valid($values->{$field_name}));
333             }
334             }
335              
336             =head2 query_param($method, \%values)
337              
338             Returns the query param for the given method C<$method> and C<\%values>.
339             Throws exception if validation fail.
340              
341             =cut
342              
343             sub query_param {
344 0     0 1 0 my ($self, $key, $values) = @_;
345              
346 0         0 my @caller = caller(0);
347 0 0       0 @caller = caller(2) if $caller[3] eq '(eval)';
348              
349 0 0       0 Method::ParamValidator::Exception::MissingMethodName->throw({
350             method => 'query_param',
351             filename => $caller[1],
352             line => $caller[2] }) unless (defined $key);
353              
354             Method::ParamValidator::Exception::InvalidMethodName->throw({
355             method => $key,
356             filename => $caller[1],
357 0 0       0 line => $caller[2] }) unless (exists $self->{methods}->{$key});
358              
359 0 0       0 Method::ParamValidator::Exception::MissingParameters->throw({
360             method => $key,
361             filename => $caller[1],
362             line => $caller[2] }) unless (defined $values);
363              
364 0 0       0 Method::ParamValidator::Exception::InvalidParameterDataStructure->throw({
365             method => $key,
366             filename => $caller[1],
367             line => $caller[2] }) unless (ref($values) eq 'HASH');
368              
369 0         0 my $method = $self->get_method($key);
370 0         0 my $fields = $method->get_fields;
371 0         0 my $query_param = '';
372 0         0 foreach my $field (@{$fields}) {
  0         0  
373 0         0 my $field_name = $field->name;
374 0         0 my $_key = "&$field_name=%" . $field->format;
375 0 0       0 $query_param .= sprintf($_key, $values->{$field_name}) if defined $values->{$field_name};
376             }
377              
378 0         0 return $query_param;
379             }
380              
381             =head2 add_field(\%param)
382              
383             Add field to the validator. Parameters are defined as below:
384              
385             +---------+-----------------------------------------------------------------+
386             | Key | Description |
387             +---------+-----------------------------------------------------------------+
388             | | |
389             | name | Unique field name. Required. |
390             | | |
391             | format | Field data type. Optional, default is 's', other valid value |
392             | | is 'd'. |
393             | | |
394             | check | Optional code ref to validate field value. |
395             | | |
396             | source | Optional hashref to validate field value against. |
397             | | |
398             | message | Optional field message. |
399             | | |
400             +---------+-----------------------------------------------------------------+
401              
402             =cut
403              
404             sub add_field {
405 10     10 1 3604 my ($self, $param) = @_;
406              
407 10 50       42 return if (exists $self->{fields}->{$param->{name}});
408              
409 10         134 $self->{fields}->{$param->{name}} = Method::ParamValidator::Key::Field->new($param);
410             }
411              
412             =head2 get_field($name)
413              
414             Returns an object of type L, matching field name C<$name>.
415              
416             =cut
417              
418             sub get_field {
419 0     0 1 0 my ($self, $name) = @_;
420              
421 0         0 my @caller = caller(0);
422 0 0       0 @caller = caller(2) if $caller[3] eq '(eval)';
423              
424 0 0       0 Method::ParamValidator::Exception::MissingFieldName->throw({
425             method => 'get_field',
426             filename => $caller[1],
427             line => $caller[2] }) unless (defined $name);
428              
429 0         0 return $self->{fields}->{$name};
430             }
431              
432             =head2 add_method(\%param)
433              
434             Add method to the validator. Parameters are defined as below:
435              
436             +---------+-----------------------------------------------------------------+
437             | Key | Description |
438             +---------+-----------------------------------------------------------------+
439             | | |
440             | name | Method name. |
441             | | |
442             | fields | Hash ref to list of fields e.g { field_1 => 1, field_2 => 0 } |
443             | | field_1 is required and field_2 is optional. |
444             | | |
445             +---------+-----------------------------------------------------------------+
446              
447             =cut
448              
449             sub add_method {
450 4     4 1 3717 my ($self, $param) = @_;
451              
452 4 50       18 return if (exists $self->{methods}->{$param->{name}});
453              
454 4         13 my $method = { name => $param->{name} };
455 4         7 foreach my $field (keys %{$param->{fields}}) {
  4         15  
456 12         24 $method->{fields}->{$field}->{object} = $self->{fields}->{$field};
457 12         24 $method->{fields}->{$field}->{required} = $param->{fields}->{$field};
458             }
459              
460 4         43 $self->{methods}->{$param->{name}} = Method::ParamValidator::Key::Method->new($method);
461             }
462              
463             =head2 get_method($name)
464              
465             Returns an object of type L, matching method name C<$name>.
466              
467             =cut
468              
469             sub get_method {
470 15     15 1 30 my ($self, $name) = @_;
471              
472 15         32 my @caller = caller(0);
473 15 50       417 @caller = caller(2) if $caller[3] eq '(eval)';
474              
475 15 50       33 Method::ParamValidator::Exception::MissingMethodName->throw({
476             method => 'get_method',
477             filename => $caller[1],
478             line => $caller[2] }) unless (defined $name);
479              
480 15         37 return $self->{methods}->{$name};
481             }
482              
483             =head1 AUTHOR
484              
485             Mohammad S Anwar, C<< >>
486              
487             =head1 REPOSITORY
488              
489             L
490              
491             =head1 BUGS
492              
493             Please report any bugs or feature requests to C,
494             or through the web interface at L.
495             I will be notified and then you'll automatically be notified of progress on your
496             bug as I make changes.
497              
498             =head1 SUPPORT
499              
500             You can find documentation for this module with the perldoc command.
501              
502             perldoc Method::ParamValidator
503              
504             You can also look for information at:
505              
506             =over 4
507              
508             =item * RT: CPAN's request tracker (report bugs here)
509              
510             L
511              
512             =item * AnnoCPAN: Annotated CPAN documentation
513              
514             L
515              
516             =item * CPAN Ratings
517              
518             L
519              
520             =item * Search CPAN
521              
522             L
523              
524             =back
525              
526             =head1 LICENSE AND COPYRIGHT
527              
528             Copyright (C) 2015 Mohammad S Anwar.
529              
530             This program is free software; you can redistribute it and / or modify it under
531             the terms of the the Artistic License (2.0). You may obtain a copy of the full
532             license at:
533              
534             L
535              
536             Any use, modification, and distribution of the Standard or Modified Versions is
537             governed by this Artistic License.By using, modifying or distributing the Package,
538             you accept this license. Do not use, modify, or distribute the Package, if you do
539             not accept this license.
540              
541             If your Modified Version has been derived from a Modified Version made by someone
542             other than you,you are nevertheless required to ensure that your Modified Version
543             complies with the requirements of this license.
544              
545             This license does not grant you the right to use any trademark, service mark,
546             tradename, or logo of the Copyright Holder.
547              
548             This license includes the non-exclusive, worldwide, free-of-charge patent license
549             to make, have made, use, offer to sell, sell, import and otherwise transfer the
550             Package with respect to any patent claims licensable by the Copyright Holder that
551             are necessarily infringed by the Package. If you institute patent litigation
552             (including a cross-claim or counterclaim) against any party alleging that the
553             Package constitutes direct or contributory patent infringement,then this Artistic
554             License to you shall terminate on the date that such litigation is filed.
555              
556             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
557             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
558             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
559             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS
560             REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT,
561             INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
562             OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
563              
564             =cut
565              
566             1; # End of Method::ParamValidator