File Coverage

blib/lib/Params/Registry.pm
Criterion Covered Total %
statement 38 82 46.3
branch 0 20 0.0
condition 0 11 0.0
subroutine 13 16 81.2
pod 2 3 66.6
total 53 132 40.1


line stmt bran cond sub pod time code
1             package Params::Registry;
2              
3 1     1   1437 use 5.010;
  1         4  
4 1     1   16 use strict;
  1         3  
  1         52  
5 1     1   6 use warnings FATAL => 'all';
  1         2  
  1         51  
6              
7 1     1   6 use Moose;
  1         6  
  1         6  
8 1     1   5154 use namespace::autoclean;
  1         10  
  1         21  
9              
10 1     1   96 use MooseX::Types::Moose qw(Maybe Str HashRef ArrayRef);
  1         4  
  1         36  
11 1     1   6099 use Params::Registry::Types qw(Template);
  1         2  
  1         20  
12              
13 1     1   7177 use MooseX::Params::Validate ();
  1         61554  
  1         37  
14              
15 1     1   14 use Params::Registry::Template;
  1         3  
  1         21  
16 1     1   6 use Params::Registry::Instance;
  1         2  
  1         22  
17 1     1   7 use Params::Registry::Error;
  1         3  
  1         19  
18              
19 1     1   773 use URI;
  1         2765  
  1         33  
20 1     1   518 use URI::QueryParam;
  1         796  
  1         1223  
21              
22             =head1 NAME
23              
24             Params::Registry - Housekeeping for sets of named parameters
25              
26             =head1 VERSION
27              
28             Version 0.09_05
29              
30             =cut
31              
32             our $VERSION = '0.09_05';
33              
34             =head1 SYNOPSIS
35              
36             use Params::Registry;
37              
38             my $registry = Params::Registry->new(
39             # express the global parameter sequence with an arrayref
40             params => [
41             {
42             # see Params::Registry::Template for the full list of
43             # attributes
44             name => 'foo',
45             },
46             ],
47             # specify groups containing potentially-overlapping subsets of
48             # parameters for different aspects of your system
49             groups => {
50             stuff => [qw(foo)],
51             },
52             # override the name of the special 'complement' parameter
53             complement => 'negate',
54             );
55              
56             my $instance = eval { $registry->process(\%params) };
57              
58             $uri->query($instance->as_string);
59              
60             =head1 DESCRIPTION
61              
62             The purpose of this module is to handle a great deal of the
63             housekeeping around sets of named parameters and their values,
64             especially as they pertain to web development. Modules like
65             L<URI::QueryParam> and L<Catalyst> will take a URI query string and
66             turn it into a HASH reference containing either scalars or ARRAY
67             references of values, but further processing is almost always needed
68             to validate the parameters, normalize them, turn them into useful
69             compound objects, and last but not least, serialize them back into a
70             canonical string representation. It is likewise important to be able
71             to encapsulate error reporting around malformed or conflicting input,
72             at both the syntactical and semantic levels.
73              
74             While this module was designed with the web in mind, it can be used
75             wherever a global registry of named parameters is deemed useful.
76              
77             =over 4
78              
79             =item Scalar
80              
81             basically untouched
82              
83             =item List
84              
85             basically untouched
86              
87             =item Tuple
88              
89             A tuple can be understood as a list of definite length, for which each
90             position has its own meaning. The contents of a tuple can likewise be
91             heterogeneous.
92              
93             =item Set
94              
95             A standard mathematical set has no duplicate elements and no concept
96             of sequence.
97              
98             =item Range
99              
100             A range can be understood as a span of numbers or number-like objects,
101             such as L<DateTime> objects.
102              
103             =item Object
104              
105             When nothing else will do
106              
107             =back
108              
109             =head3 Cascading
110              
111             There are instances, for example in the case of supporting a legacy
112             HTML form, when it is useful to combine input parameters. Take for
113             instance the practice of using drop-down boxes for the year, month and
114             day of a date in lieu of support for the HTML5 C<datetime> form field,
115             or access to custom form controls. One would specify C<year>, C<month>
116             and C<day> parameters, as well as a C<date> parameter which
117             C<consumes> the former three, C<using> a subroutine reference to do
118             it. Consumed parameters are deleted from the set.
119              
120             =head3 Complement
121              
122             A special parameter, C<complement>, is defined to signal parameters in
123             the set itself which should be treated as complements to what have
124             been expressed in the input. This module makes no prescriptions about
125             how the complement is to be interpreted, with the exception of
126             parameters whose values are bounded sets or ranges: if a shorter query
127             string can be achieved by negating the set and removing (or adding)
128             the parameter's name to the complement, that is what this module will
129             do.
130              
131             # universe of foo = (a .. z)
132             foo=a&foo=b&foo=c&complement=foo -> (a .. z) - (a b c)
133              
134             =head1 METHODS
135              
136             =head2 new
137              
138             Instantiate a new parameter registry.
139              
140             =head3 Arguments
141              
142             =over 4
143              
144             =item params
145              
146             An C<ARRAY> reference of C<HASH> references, containing the specs to
147             be passed into L<Params::Registry::Template> objects.
148              
149             =cut
150              
151             around BUILDARGS => sub {
152             my $orig = shift;
153             my $class = shift;
154              
155             my %p = MooseX::Params::Validate::validated_hash(
156             \@_,
157             params => { isa => 'ArrayRef[Maybe[HashRef]]' },
158             complement => { isa => 'Maybe[Str]', optional => 1 },
159             groups => { isa => 'HashRef[ArrayRef[Maybe[Str]]]', optional => 1 },
160             );
161              
162             # fiddle with input and output
163             my @entries = @{delete $p{params}};
164             $p{params} = {};
165              
166             # pass once to separate the templates from their sequence
167             my (@seq, %map);
168             for my $entry (@entries) {
169             my $name = delete $entry->{name};
170             push @seq, $name;
171             if (my $use = delete $entry->{use}) {
172             # TODO: recursive
173             $map{$name} = $use;
174             }
175             # TODO: throw a proper error on duplicate key
176             Params::Registry::Error->throw
177             ("Parameter $name already exists") if exists $p{params}{$name};
178              
179             $p{params}{$name} = $entry;
180             }
181              
182             # second pass to stitch the reused parameters together
183             while (my ($k, $v) = each %map) {
184             # TODO throw a proper error if the target isn't found
185             my $p = $p{params}{$v} or Params::Registry::Error->throw
186             ("Tried to resolve $v for reuse but couldn't find it");
187              
188             # overwrite with any new data
189             $p{params}{$k} = {%$p, %{$p{params}{$k}}};
190             }
191              
192             # add param sequence to BUILD
193             $p{_sequence} = \@seq;
194              
195             $class->$orig(%p);
196             };
197              
198             sub BUILD {
199 0     0 0   my $self = shift;
200 0           my $p = $self->_params;
201              
202 0           my @seq = @{$self->_sequence};
  0            
203 0           my (%rank, @stack);
204 0           for my $k (@seq) {
205 0           my %t = %{$p->{$k}};
  0            
206 0           my $x = $p->{$k} = Params::Registry::Template->new
207             (%t, registry => $self);
208 0 0         if ($x->_consdep > 0) {
209             # shortcut because only parameters with dependencies will
210             # have a rank higher than zero
211 0           $rank{$k} = 1;
212 0           push @stack, $k;
213             }
214             else {
215 0           $rank{$k} = 0;
216             }
217             }
218              
219             # construct a rank tree
220              
221             #my %seen;
222 0           while (my $x = shift @stack) {
223             #my %c = map { $_ => 1 } $p->{$x}->consumes;
224 0           my $match = 0;
225 0           for my $c ($p->{$x}->_consdep) {
226 0 0         $match = 1 if $rank{$x} == $rank{$c};
227             # XXX will this actually catch all cycles?
228             Params::Registry::Error->throw
229 0 0         ("Cycle detected between $x and $c") if $rank{$x} < $rank{$c};
230             }
231              
232 0 0         if ($match) {
233 0           $rank{$x}++;
234 0           push @stack, $x;
235             }
236             }
237              
238             # this makes an array of arrays of key names, in the order to be
239             # processed.
240 0           my $r = $self->_ranked;
241 0           for my $k (@seq) {
242 0   0       my $x = $r->[$rank{$k}] ||= [];
243 0           push @$x, $k;
244             }
245             # note that any global sequence here would be valid as long as it
246             # didn't put a consuming param before one to be consumed
247              
248             # XXX it is currently unclear whether two consuming parameters can
249             # consume the same parameter.
250              
251             # we should also do deps and conflicts here:
252              
253             # deps are transitive but asymmetric; A -> B -> C implies A -> C
254             # but says nothing about C
255              
256             # conflicts are symmetric: A conflicts with B means B conflicts
257             # with A.
258              
259             # it is nonsensical (and therefore illegal) for parameters to
260             # simultaneously depend and conflict.
261              
262             #warn Data::Dumper::Dumper($self->_ranked);
263             }
264              
265             has _params => (
266             is => 'ro',
267             #isa => HashRef[Template],
268             isa => HashRef,
269             traits => [qw(Hash)],
270             #coerce => 1,
271             required => 1,
272             init_arg => 'params',
273             handles => {
274             template => 'get',
275             },
276             );
277              
278             has _sequence => (
279             is => 'ro',
280             traits => [qw(Array)],
281             isa => ArrayRef[Str],
282             required => 1,
283             handles => {
284             sequence => 'elements',
285             },
286             );
287              
288             has _ranked => (
289             is => 'ro',
290             # traits => [qw(Array)],
291             isa => ArrayRef[ArrayRef[Str]],
292             lazy => 1,
293             default => sub { [] },
294             # required => 1,
295             # handles => {
296             # sequence => 'elements',
297             # },
298             );
299              
300             =item groups
301              
302             A C<HASH> reference such that the keys are names of groups, and the
303             values are C<ARRAY> references of parameters to include in each group.
304              
305             =cut
306              
307             has _groups => (
308             is => 'ro',
309             isa => HashRef[Maybe[ArrayRef[Maybe[Str]]]],
310             lazy => 1,
311             default => sub { {} },
312             init_arg => 'groups',
313             );
314              
315             =item complement
316              
317             This is the I<name> of the special parameter used to indicate which
318             I<other> parameters should have a
319             L<Params::Registry::Template/complement> operation run over them. The
320             default name, naturally, is C<complement>. This parameter will always
321             be added to the query string last.
322              
323             =cut
324              
325             has complement => (
326             is => 'ro',
327             isa => Str,
328             lazy => 1,
329             default => 'complement',
330             );
331              
332             =back
333              
334             =head2 process $STR | $URI | \%PARAMS
335              
336             Turn a L<URI>, query string or C<HASH> reference (such as those found
337             in L<Catalyst> or L<URI::QueryParam>) into a
338             L<Params::Registry::Instance>. May croak.
339              
340             =cut
341              
342             sub process {
343 0     0 1   my $self = shift;
344              
345 0           my $obj;
346 0 0 0       if (ref $_[0]) {
    0 0        
    0          
347 0 0 0       if (Scalar::Util::blessed($_[0]) and $_[0]->isa('URI')) {
    0          
348 0           $obj = $_[0]->query_form_hash;
349             }
350             elsif (ref $_[0] eq 'HASH') {
351 0           $obj = $_[0];
352             }
353             else {
354 0           Params::Registry::Error->throw
355             ('If the argument is a ref, it must be a URI or a HASH ref');
356             }
357             }
358             elsif (@_ == 1 && defined $_[0]) {
359 0           my $x = $_[0];
360 0 0         $x = "?$x" unless $x =~ /^\?/;
361 0           $obj = URI->new("http://foo/$x")->query_form_hash;
362             }
363             elsif (@_ > 0 && @_ % 2 == 0) {
364 0           my %x = @_;
365 0           $obj = \%x;
366             }
367             else {
368 0           Params::Registry::Error->throw
369             ('Check your inputs to Params::Registry::process');
370             }
371              
372 0           my $instance = Params::Registry::Instance->new(registry => $self);
373              
374 0           $instance->set($obj, -defaults => 1, -force => 1);
375             }
376              
377             =head2 template $KEY
378              
379             Return a particular template from the registry.
380              
381             =head2 sequence
382              
383             Return the global sequence of parameters for serialization.
384              
385             =head2 refresh
386              
387             Refresh the stateful components of the templates
388              
389             =cut
390              
391             sub refresh {
392 0     0 1   my $self = shift;
393 0           for my $template (values %{$self->_params}) {
  0            
394 0           $template->refresh;
395             }
396             }
397              
398             =head1 AUTHOR
399              
400             Dorian Taylor, C<< <dorian at cpan.org> >>
401              
402             =head1 BUGS
403              
404             Please report any bugs or feature requests to C<bug-params-registry at
405             rt.cpan.org>, or through the web interface at
406             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Params-Registry>. I
407             will be notified, and then you'll automatically be notified of
408             progress on your bug as I make changes.
409              
410             =head1 SUPPORT
411              
412             You can find documentation for this module with the perldoc command.
413              
414             perldoc Params::Registry
415              
416              
417             You can also look for information at:
418              
419             =over 4
420              
421             =item * RT: CPAN's request tracker (report bugs here)
422              
423             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Params-Registry>
424              
425             =item * AnnoCPAN: Annotated CPAN documentation
426              
427             L<http://annocpan.org/dist/Params-Registry>
428              
429             =item * CPAN Ratings
430              
431             L<http://cpanratings.perl.org/d/Params-Registry>
432              
433             =item * Search CPAN
434              
435             L<http://search.cpan.org/dist/Params-Registry/>
436              
437             =back
438              
439             =head1 SEE ALSO
440              
441             =over 4
442              
443             =item
444              
445             L<Params::Registry::Instance>
446              
447             =item
448              
449             L<Params::Registry::Template>
450              
451             =item
452              
453             L<Params::Validate>
454              
455             =back
456              
457             =head1 LICENSE AND COPYRIGHT
458              
459             Copyright 2013 Dorian Taylor.
460              
461             Licensed under the Apache License, Version 2.0 (the "License"); you
462             may not use this file except in compliance with the License. You may
463             obtain a copy of the License at
464             L<http://www.apache.org/licenses/LICENSE-2.0> .
465              
466             Unless required by applicable law or agreed to in writing, software
467             distributed under the License is distributed on an "AS IS" BASIS,
468             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
469             implied. See the License for the specific language governing
470             permissions and limitations under the License.
471              
472              
473             =cut
474              
475             __PACKAGE__->meta->make_immutable;
476              
477             1; # End of Params::Registry