File Coverage

blib/lib/Params/Validate/Dependencies.pm
Criterion Covered Total %
statement 104 104 100.0
branch 32 34 94.1
condition 18 21 85.7
subroutine 25 25 100.0
pod 3 3 100.0
total 182 187 97.3


line stmt bran cond sub pod time code
1             package Params::Validate::Dependencies;
2              
3 11     11   76912 use strict;
  11         63  
  11         329  
4 11     11   60 use warnings;
  11         24  
  11         311  
5              
6 11     11   4463 use Clone qw(clone);
  11         27125  
  11         629  
7 11     11   5643 use Params::Validate (); # don't import yet
  11         90409  
  11         374  
8 11     11   4643 use Params::Validate::Dependencies::Documenter;
  11         37  
  11         345  
9 11     11   69 use Scalar::Util qw(blessed);
  11         33  
  11         486  
10 11     11   4666 use PadWalker qw(closed_over);
  11         6686  
  11         666  
11              
12 11     11   94 use base qw(Exporter);
  11         34  
  11         1612  
13              
14 11     11   78 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS $DOC);
  11         30  
  11         1726  
15              
16             $VERSION = '1.41';
17             $DOC = 0;
18              
19             # copy and update P::V's EXPORT* constants
20             my @_of = qw(any_of all_of none_of one_of);
21             @EXPORT = (@Params::Validate::EXPORT, @_of);
22             @EXPORT_OK = (@Params::Validate::EXPORT_OK, @_of, 'exclusively');
23             %EXPORT_TAGS = (%{clone(\%Params::Validate::EXPORT_TAGS)}, _of => \@_of);
24             push @{$EXPORT_TAGS{all}}, (@_of, 'exclusively');
25              
26             # because repeating the call to _validate_factory_args everywhere is BAD
27             foreach my $sub (@_of, 'exclusively') {
28 11     11   80 no strict 'refs';
  11         28  
  11         412  
29 11     11   66 no warnings 'redefine';
  11         25  
  11         16609  
30             my $orig = \&{$sub};
31             *{$sub} = sub {
32 44     44   4881 local *__ANON__ = $sub;
33 44         162 _validate_factory_args(@_);
34 43         172 $orig->(@_);
35             };
36             }
37              
38             sub import {
39             # import all of P::V except validate() and dvalidate_with()
40 14     14   127 Params::Validate->import(grep { ! /^validate(_with)?$/ } @Params::Validate::EXPORT_OK);
  224         1634  
41             # now export all that P::V would have exported, plus *_of
42 14         4575 __PACKAGE__->export_to_level(1, @_);
43             }
44              
45             =head1 NAME
46              
47             Params::Validate::Dependencies - check that the right combination of arguments is passed to a function
48              
49             =head1 DESCRIPTION
50              
51             Extends Params::Validate to make it easy to validate
52             that you have been passed the correct combinations of parameters.
53              
54             =head1 SYNOPSIS
55              
56             This example validates that sub 'foo's arguments are of the right types,
57             and that either we have at least one of alpha, beta and gamma, or
58             we have both of bar amd baz:
59              
60             use Params::Validate::Dependencies qw(:all);
61              
62             sub foo {
63             validate(@_,
64             {
65             alpha => { type => ARRAYREF, optional => 1 },
66             beta => { type => ARRAYREF, optional => 1 },
67             gamma => { type => ARRAYREF, optional => 1 },
68             bar => { type => SCALAR, optional => 1 },
69             baz => { type => SCALAR, optional => 1 },
70             },
71             any_of(
72             qw(alpha beta gamma),
73             all_of(qw(bar baz)),
74             )
75             );
76             }
77              
78             =head1 HOW IT WORKS
79              
80             Params::Validate::Dependencies extends Params::Validate's
81             validate() function to
82             support an arbitrary number of callbacks which are not associated
83             with any one parameter. All of those callbacks are run after
84             Params::Validate's normal validate() function.
85             If any of them return false, then validate() will die as normal.
86              
87             =head1 SUBROUTINES and EXPORTS
88              
89             All of the *_of functions are exported by default in addition to those
90             exported by default by Params::Validate. They are also available with the
91             tag ':_of' in case you want to use them without Params::Validate.
92             In that case you would load the module thus:
93              
94             use Params::Validate::Dependencies qw(:_of);
95              
96             All of the *_of functions take a list of scalars and code-refs and
97             return a code-ref (which is a closure over the list originally passed
98             to the function) suitable for use in validate() or in another *_of
99             function. All code-refs should take as their only argument a hashref
100             of parameters to check, returning true if the parameters are good
101             and false otherwise.
102              
103             =head2 document
104              
105             This takes a code-ref argument as generated by a tree of *_of calls,
106             and spits out some documentation of it. This function is not exported.
107              
108             =cut
109              
110             sub document {
111 40     40 1 799 my $sub = shift;
112 40 100 66     390 die(__PACKAGE__."::document: object $sub is wrong type\n")
113             unless(blessed($sub) && $sub->can('_document'));
114 34         148 $sub->_document();
115             }
116              
117             =head2 validate
118              
119             Overrides and extends Params::Validate's function of the same name.
120              
121             =cut
122              
123             sub validate (\@@) {
124 46     46 1 11832 my @args;
125              
126 46         92 my $p = shift;
127 46 50       175 if ( ref $p eq 'ARRAY' ) {
128             # First argument might have been a hash reference
129 46 100       84 @args = @{ ref $p->[0] ? [ %{ $p->[0] } ] : $p };
  46         175  
  1         5  
130             }
131              
132 46         78 my $pv_spec;
133 46 100 66     295 if(ref($_[0]) && ref($_[0]) =~ /HASH/) {
134 23         46 $pv_spec = shift;
135             }
136 46         100 my @coderefs = @_;
137              
138 46         127 my %rval = @args;
139             # P::V::validate may alter it by applying defaults
140 46 100       608 %rval = Params::Validate::validate(@args, $pv_spec) if($pv_spec);
141              
142 44         147 foreach (@coderefs) {
143 41 100       173 die 'code-ref checking failed: arguments were not ' . document($_) . "\n"
144             unless $_->({@args});
145             }
146              
147 25 100       225 return wantarray ? %rval : \%rval;
148             }
149              
150             =head2 validate_with
151              
152             Overrides and extends Params::Validate's function of the same name.
153              
154             The code-ref, or an array-ref of code-refs, are passed as the
155             extra 'dependencies' argument.
156              
157             =cut
158              
159             sub validate_with {
160 5     5 1 106 my %args = @_;
161 5         10 my $params = [ @{$args{params}} ];
  5         16  
162              
163 5 100       18 $args{dependencies} = [] unless defined $args{dependencies};
164 5         10 my $coderefs = delete $args{dependencies};
165 5 100       19 $coderefs = ref($coderefs) eq 'ARRAY' ? $coderefs : [ $coderefs ];
166              
167 5         89 my %rval = Params::Validate::validate_with(@_);
168              
169 5         23 foreach (@{$coderefs}) {
  5         15  
170 4 100       6 die('code-ref checking failed: arguments were not '.document($_)."\n") unless($_->({@{$params}}));
  4         19  
171             }
172              
173 4 50       47 return wantarray ? %rval : \%rval;
174             }
175              
176             =head2 exclusively
177              
178             Takes a single subref as its only argument (this would normally be the
179             results of one of the *_of functions), and returns a code-ref which
180             returns true if the hashref it is given only contains fields
181             mentioned in the original function or any of its children. For example
182             ...
183              
184             validate(@_,
185             exclusively(
186             any_of(
187             qw(alpha beta gamma),
188             all_of(qw(bar baz)),
189             )
190             )
191             );
192              
193             will not tolerate arguments such as:
194              
195             bar => ...,
196             baz => ...,
197             sheep => ...
198              
199             because sheep aren't mentioned in the 'any_of' and 'all_of's. Internally
200             this uses the auto-documenter interface to interrogate the child sub. This
201             means that if you want to use C with third-party extensions
202             then they must support auto-documentation.
203              
204             This function is not exported by default but can be.
205              
206             =cut
207              
208             sub exclusively {
209             my @options = @_;
210             my $childsub = shift;
211             _bless_right_class(
212             sub {
213             my $documentation = document($childsub);
214             if($Params::Validate::Dependencies::DOC) {
215             return "exclusively ($documentation)";
216             }
217              
218             my @strings = map {
219             s/\\'/'/g; $_
220             } $documentation =~ /
221             '
222             (
223             (?:\\'|[^'])+
224             )
225             '
226             /xg;
227              
228             my %params = %{shift()};
229             foreach my $param (keys %params) {
230             return 0 if(!grep { $param eq $_ } @strings);
231             }
232             return 1;
233             }
234             );
235             }
236              
237             =head2 none_of
238              
239             Returns a code-ref which checks that the hashref it receives matches
240             none of the options given.
241              
242             You might want to use it thus:
243              
244             all_of(
245             'alpha',
246             none_of(qw(bar baz))
247             )
248              
249             to validate that 'alpha' must *not* be accompanied by 'bar' or 'baz'.
250              
251             =cut
252              
253             sub none_of {
254             my @options = @_;
255             _bless_right_class(_mk_autodoc(sub { _count_of(\@options, 0)->(@_) }));
256             }
257              
258             =head2 one_of
259              
260             Returns a code-ref which checks that the hashref it receives matches
261             only one of the options given.
262              
263             =cut
264              
265             sub one_of {
266             my @options = @_;
267             _bless_right_class(_mk_autodoc(sub { _count_of(\@options, 1)->(@_) }));
268             }
269              
270             =head2 any_of
271              
272             Returns a code-ref which checks that the hashref it receives matches
273             one or more of the options given.
274              
275             =cut
276              
277             sub any_of {
278             my @options = @_;
279             _bless_right_class(_mk_autodoc(sub {
280             my %params = %{shift()};
281             foreach my $option (@options) {
282             return 1 if(!ref($option) && exists($params{$option}));
283             return 1 if(ref($option) && $option->(\%params));
284             }
285             return 0;
286             }));
287             }
288              
289             =head2 all_of
290              
291             Returns a code-ref which checks that the hashref it receives matches
292             all of the options given.
293              
294             =cut
295              
296             sub all_of {
297             my @options = @_;
298              
299             _bless_right_class(_mk_autodoc(sub { _count_of(\@options, $#options + 1)->(@_) }));
300             }
301              
302             # {none,one,all}_of are thin wrappers around this
303             sub _count_of {
304 57     57   105 my @options = @{shift()};
  57         153  
305 57         111 my $desired_count = shift;
306             sub {
307 57     57   97 my %params = %{shift()};
  57         195  
308 57         112 my $matches = 0;
309 57         116 foreach my $option (@options) {
310             $matches++ if(
311 125 100 100     586 (!ref($option) && exists($params{$option})) ||
      100        
      100        
312             (ref($option) && $option->(\%params))
313             );
314             }
315 57         452 return ($matches == $desired_count);
316             }
317 57         337 }
318              
319             sub _mk_autodoc {
320 41     41   92 my $sub = shift;
321             sub {
322 144 100   144   531 if($DOC) {
323 70         1001 return $DOC->_doc_me(list => (closed_over($sub))[0]->{'@options'});
324             }
325 74         188 $sub->(@_);
326             }
327 41         206 }
328              
329             sub _bless_right_class {
330 43     43   255 my($sub, $class) = (shift(), (caller(1))[3]);
331 43         291 (my $subname = $class) =~ s/.*:://;
332 11     11   115 no strict 'refs';
  11         30  
  11         3119  
333 43 100       89 unless(@{"${class}::ISA"}) {
  43         335  
334 26         59 @{"${class}::ISA"} = ('Params::Validate::Dependencies::Documenter');
  26         445  
335 26     76   190 *{"${class}::name"} = sub { $subname };
  26         181  
  76         348  
336 26 100   57   125 *{"${class}::join_with"} = sub { $subname eq 'all_of' ? 'and' : 'or' };
  26         138  
  57         270  
337             }
338 43         301 bless $sub, $class;
339             }
340              
341             sub _validate_factory_args {
342 44     44   135 my @options = @_;
343 44         293 my $sub = (caller(1))[3];
344             die("$sub takes only SCALARs, code-refs, and Params::Validate::Dependencies::* objects\n")
345             if(grep {
346 44 100 66     127 ref($_) && ref($_) !~ /CODE/ &&
  102 100 100     695  
347             !(blessed($_) && $_->isa('Params::Validate::Dependencies::Documenter'))
348             } @options);
349             }
350              
351             =head1 LIES
352              
353             Some of the above is incorrect. If you really want to know what's
354             going on, look at L.
355              
356             =head1 BUGS, LIMITATIONS, and FEEDBACK
357              
358             I like to know who's using my code. All comments, including constructive
359             criticism, are welcome.
360              
361             Please report any bugs either by email or
362             at L.
363              
364             Any incompatibility with Params::Validate will be considered to be a bug,
365             with the exception of minor differences in error messages.
366              
367             Bug reports should contain enough detail that I can replicate the
368             problem and write a test. The best bug reports have those details
369             in the form of a .t file. If you also include a patch I will love
370             you for ever.
371              
372             =head1 SEE ALSO
373              
374             L
375              
376             L
377              
378             =head1 SOURCE CODE REPOSITORY
379              
380             L
381              
382             L
383              
384             =head1 COPYRIGHT and LICENCE
385              
386             Copyright 2023 David Cantrell EFE
387              
388             This software is free-as-in-speech software, and may be used, distributed, and modified under the terms of either the GNU General Public Licence version 2 or the Artistic Licence. It's up to you which one you use. The full text of the licences can be found in the files GPL2.txt and ARTISTIC.txt, respectively.
389              
390             =head1 CONSPIRACY
391              
392             This module is also free-as-in-mason.
393              
394             =cut
395              
396             1;