File Coverage

lib/Catalyst/Plugin/Params/Profile.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Catalyst::Plugin::Params::Profile - Parameter checking with Params::Profile
5              
6             =head1 SYNOPSIS
7              
8             package MyAPP;
9             use Catalyst qw/Params::Profile/;
10              
11             # In a controller
12             MyAPP->register_profile(
13             'method' => 'subroto',
14             'profile' => {
15             testkey1 => { required => 1 },
16             testkey2 => {
17             required => 1,
18             allow => qr/^\d+$/,
19             },
20             testkey3 => {
21             allow => qr/^\w+$/,
22             },
23             },
24             );
25              
26             sub subroto : Private {
27             my (%params) = @_;
28              
29             return unlesss $c->validate('params' => \%params);
30             ### OR
31             my %opts = $c->check_params or return;
32              
33             ### DO SOME STUFF HERE ...
34              
35             my $profile = $c->get_profile('method' => 'subroto');
36             }
37              
38              
39             ### Multiple Profile
40             MyAPP->register_profile(
41             'method' => 'subalso',
42             'profile' => [
43             'subroto',
44             {
45             testkey4 => { required => 1 },
46             testkey5 => {
47             required => 1,
48             allow => qr/^\d+$/,
49             },
50             testkey6 => {
51             allow => qr/^\w+$/,
52             },
53             },
54             ],
55             );
56              
57              
58             sub subalso : Local {
59             my (%params) = @_;
60              
61             ### Checks parameters agains profile of subroto and above registered
62             ### profile
63             return unlesss $c->validate('params' => \%params);
64              
65             ### DO SOME STUFF HERE ...
66             }
67              
68              
69             =head1 DESCRIPTION
70              
71             Catalyst::Plugin::Params::Profile provides a mechanism for a centralised
72             Params::Check or a Data::FormValidater profile. You can bind a profile to a
73             class::subroutine, then, when you are in a subroutine you can simply call
74             $c->check($params) of $c->validate($params) to validate against this profile.
75              
76             For more information read the manual of C<Params::Profile> , the methods below
77             are just an interface on it.
78              
79             =head1 Public Methods
80              
81             See C<Params::Profile> for more information about the specific methods,
82             the onse listed below are the most important.
83              
84             =over 4
85              
86             =item $c->register_profile('method' => $METHOD, 'profile' => \%PROFILE);
87              
88             =item $c->get_profile('method' => $METHOD);
89              
90             =item $c->validate('params' => \%PARAMS);
91              
92             =item $c->check('params' => \%PARAMS);
93              
94             =back
95              
96             =head2 $c->check_params;
97              
98             Checks $c->req->params against the profile of the calling sub. It returns
99             a HASHREF containing validate parameters, or undef on failure. It will also
100             log to Catalyst::Log about what happened.
101              
102             Extra options to this sub are:
103              
104             =over 4
105              
106             =item params OPTIONAL
107              
108             Validate against params instead of $c->req->params
109              
110             =item profile OPTIONAL
111              
112             Validate against profile instead of profile of calling sub
113              
114             =item allow_unknown OPTIONAL
115              
116             Pass the unknown parameters to the return HREF rather than filtering them out.
117              
118             =item dv OPTIONAL
119              
120             Instead of returning a hashref containing validated params, return the
121             Data::FormValidator::Results object.
122              
123             NOTE: Only tested with Data::FormValidator yet!!
124              
125             =back
126              
127             =head1 XMLRPC
128              
129             This module also registers a method name called C<system.methodHelp> into
130             the Server::XMLRPC plugin when it is loaded. This method will try to explain
131             in plaintext the arguments for the subroutine by parsing the given profile.
132              
133             Example output when system.methodHelp is called with one argument containing
134             the name of the method you'd like to be explained:
135              
136             Required arguments are:
137             * customer_id
138             * username
139             * product
140              
141             Optional arguments are:
142             * roaming
143             * card_type
144              
145             NOTE: This currently only works for Data::FormValidator profiles.
146              
147             =cut
148              
149             { package Catalyst::Plugin::Params::Profile;
150              
151 1     1   22672 use strict;
  1         3  
  1         40  
152 1     1   5 use warnings;
  1         2  
  1         39  
153              
154 1     1   446 use Catalyst::ActionContainer;
  0            
  0            
155             use Tree::Simple;
156             use Class::C3;
157              
158             our $VERSION = '0.05';
159              
160             use base qw/Params::Profile/;
161              
162             ### Override _raise_warning of paramsprofile to log to Catalyst
163             ### debug engine
164             sub _raise_warning {
165             my ($c, $warning) = @_;
166              
167             ### Do not warn on missing profile, this will be shown in a nice
168             ### formatted table in debug mode ;)
169             return if $warning =~ /No profile for/;
170              
171             $c->log->debug($warning) if $c->debug;
172             }
173              
174              
175             ### Extend setup_actions to check Params::Profile registered methods,
176             ### and generate a nice table containing Params::Profile specific
177             ### informatie. We will also register a method 'system.methodHelp' into
178             ### the Server::XMLRPC module when this module is available.
179             sub setup_actions {
180             my $c = shift;
181             $c->next::method( @_ );
182              
183             ### Generate nice table containing Params::Profile specific
184             ### information
185             $c->_check_profiles;
186              
187             $c->error('WARNING: Profiles are not correct!') unless
188             Params::Profile->verify_profiles;
189              
190             if ($c->registered_plugins('Server::XMLRPC')) {
191             $c->server->xmlrpc->add_private_method('system.methodHelp', sub
192             {
193             my ($class, @args) = @_;
194             my $action = $class->server->xmlrpc->dispatcher->{
195             'Path'
196             }->methods->{$args[0]};
197             my $ns = $action->class .'::'. $action->name;
198             $class->stash->{xmlrpc} = $class->_describe_pp_plaintext(
199             profile => $ns,
200             );
201             }
202             );
203             }
204             }
205              
206             sub _check_profiles {
207             my ($c) = @_;
208             my $actions = {};
209              
210             for my $controller ($c->controllers) {
211             my @containers = $c->dispatcher->get_containers($c->controller($controller)->action_namespace);
212              
213             for my $container (@containers) {
214             my $container_actions = $container->actions;
215              
216             for my $action ( keys %{ $container_actions } ) {
217             next if $action =~ /^_.*/;
218              
219             my $action_obj = $container_actions->{$action};
220              
221             $actions->{
222             $action_obj->class . '::' . $action_obj->name
223             } = $action_obj;
224             }
225             }
226             }
227              
228             return 1 if scalar(keys %{ $actions }) < 1;
229              
230             { # Table creation
231             my $nogotable = Text::SimpleTable->new(
232             [ 20, 'Private'],
233             [ 38, 'Class' ],
234             [ 12, 'Method' ],
235             );
236              
237             my $show_nogotable;
238             foreach my $method (%{$actions}) {
239             my $action = $actions->{$method};
240             next unless (
241             $action &&
242             !$action->attributes->{Private} &&
243             !$c->get_profile('method' => $method)
244             );
245             $show_nogotable = 1;
246             $nogotable->row(
247             '/'.$action->reverse,
248             $action->class,
249             $action->name,
250             );
251             }
252              
253             $c->log->debug("WARNING: Missing profiles:\n" . $nogotable->draw)
254             if $c->debug && $show_nogotable;
255             }
256             }
257              
258             sub _describe_pp_plaintext {
259             my ($self, %opts) = @_;
260             my ($profile,$txt);
261              
262             ### Check if option profile is a profile or a methodname
263             if (!UNIVERSAL::isa($opts{profile},'HASH')) {
264             $profile = $self->get_profile(method => $opts{profile}) or return;
265             } else {
266             $profile = $opts{profile};
267             }
268              
269             ### Check for Data::FormValidator profile
270             return 'No help available for this method' unless
271             UNIVERSAL::isa($profile->{required}, 'ARRAY') ||
272             UNIVERSAL::isa($profile->{optional}, 'ARRAY');
273              
274             ### Create describe string
275             if ($profile->{required}) {
276             $txt .= "Required arguments are:";
277             $txt .= "\n * " . $_ for @{$profile->{required}};
278             $txt .= "\n\n";
279             }
280             if ($profile->{optional}) {
281             $txt .= "Optional arguments are:";
282             $txt .= "\n * " . $_ for @{$profile->{optional}};
283             $txt .= "\n\n";
284             }
285             if ($profile->{constraint_methods} && $opts{constraints}) {
286             $txt .= "Given constraints are:";
287             while (my ($key, $value) = each %{$profile->{constraint_methods}}) {
288             $txt .= "\n $key => " . scalar($value);
289             }
290             }
291              
292             return $txt;
293             }
294              
295             sub check_params {
296             my ($c, %args) = @_;
297             my (%ok_params);
298             ### Extra option: allow_unknown
299             ### allow_unknown: do not filter out parameters we do not know
300              
301             ### Get options
302             my %params = $args{params} || %{ $c->req->params };
303              
304             ### Get caller
305             my $caller_sub = $args{method} || [caller(1)]->[3];
306              
307             local $Params::Check::VERBOSE = undef;
308             local $Params::Check::ALLOW_UNKNOWN = 1 if $args{allow_unknown};
309              
310             my $result = $c->check(
311             method => $caller_sub,
312             params => \%params,
313             );
314              
315             if (
316             UNIVERSAL::isa($result, 'Data::FormValidator::Results')
317             ) {
318             my $dv = $result;
319              
320             ### Go into error when parameters validate AND
321             ### we have no unknown params or pass_unknown is 1
322             unless ($dv->success) {
323             my $errmsg = "Problems validating profile:";
324             ### Go log something
325             $errmsg .= "\n Missing params:\n * " .
326             join("\n * ", $dv->missing)
327             if $dv->has_missing;
328             $errmsg .= "\n Invalid params:\n * " .
329             join("\n * ", $dv->invalid)
330             if $dv->has_invalid;
331             $errmsg .= "\n Unknown params:\n * " .
332             join("\n * ", $dv->unknown)
333             if $dv->has_unknown;
334             $c->log->debug($errmsg) if $c->debug;
335             return;
336             }
337              
338             return $dv if $args{dv};
339              
340             ### Force dv->valid to return as href
341             my $href = $dv->valid;
342              
343             ### Return all params or only validated ones.
344             return $args{allow_unknown} ? \%params : $href;
345             } else {
346             if (my $errmsg = Params::Check::last_error()) {
347             $c->log->debug("Problems validating profile:\n" .
348             Params::Check::last_error()) if $c->debug;
349             }
350             return $result;
351             }
352              
353             return;
354             }
355             }
356              
357             1;
358              
359             __END__
360              
361             =head1 AUTHOR
362              
363             Michiel Ootjers E<lt>michiel@cpan.orgE<gt>.
364              
365             and
366              
367             Jos Boumans E<lt>kane@cpan.orgE<gt>.
368              
369             =head1 TODO
370              
371             =over 4
372              
373             =item profile explanation
374              
375             Fix the profile explanation to explain profiles other than
376             C<Data::FormValidator>
377              
378             =back
379              
380             =head1 BUG REPORTS
381              
382             Please submit all bugs regarding C<Catalyst::Plugin::Params::Profile> to
383             C<bug-catalyst-plugin-params-profile@rt.cpan.org>
384              
385             =head1 SOURCE
386              
387             Please send your improvements as pull request via bitbucket.org to
388             C<https://bitbucket.org/michielootjers/catalyst-plugin-params-profile>
389              
390             =head1 COPYRIGHT
391              
392             This module is
393             copyright (c) 2002-2013 Michiel Ootjers E<lt>michiel@cpan.orgE<gt>.
394             All rights reserved.
395              
396             This library is free software;
397             you may redistribute and/or modify it under the same
398             terms as Perl itself.
399              
400             =cut