File Coverage

blib/lib/Params/Registry/Instance.pm
Criterion Covered Total %
statement 26 197 13.2
branch 0 104 0.0
condition 0 45 0.0
subroutine 9 24 37.5
pod 9 9 100.0
total 44 379 11.6


line stmt bran cond sub pod time code
1             package Params::Registry::Instance;
2              
3 1     1   1429 use 5.010;
  1         3  
4 1     1   7 use strict;
  1         2  
  1         30  
5 1     1   5 use warnings FATAL => 'all';
  1         2  
  1         49  
6              
7 1     1   8 use Moose;
  1         3  
  1         38  
8 1     1   7072 use namespace::autoclean;
  1         2  
  1         11  
9              
10 1     1   79 use Params::Registry::Error;
  1         2  
  1         20  
11 1     1   589 use URI::Escape ();
  1         1572  
  1         25  
12              
13 1     1   7 use Scalar::Util ();
  1         2  
  1         18  
14 1     1   4 use Try::Tiny;
  1         3  
  1         3110  
15              
16             #use constant INF => 100**100**100;
17             #use constant NEG_INF => 1 - INF;
18              
19             =head1 NAME
20              
21             Params::Registry::Instance - An instance of registered parameters
22              
23             =head1 VERSION
24              
25             Version 0.04_03
26              
27             =cut
28              
29             our $VERSION = '0.04_03';
30              
31             has registry => (
32             is => 'ro',
33             isa => 'Params::Registry',
34             required => 1,
35             weak_ref => 1,
36             #init_arg => 'registry',
37             );
38              
39             sub _registry {
40 0     0     $_[0]->registry;
41             }
42              
43             has _content => (
44             is => 'ro',
45             isa => 'HashRef',
46             traits => [qw(Hash)],
47             lazy => 1,
48             default => sub { {} },
49             handles => {
50             exists => 'exists',
51             # get => 'get',
52             keys => 'keys',
53             },
54             );
55              
56             has _other => (
57             is => 'ro',
58             isa => 'HashRef',
59             traits => [qw(Hash)],
60             lazy => 1,
61             default => sub { {} },
62             handles => {
63             },
64             );
65              
66              
67             =head1 SYNOPSIS
68              
69             use Params::Registry;
70             use URI;
71             use URI::QueryParam;
72              
73             my $registry = Params::Registry->new(%enormous_arg_list);
74              
75             my $uri = URI->new($str);
76              
77             # The instance is created through Params::Registry, which will
78             # raise different exceptions for different types of conflict in
79             # the parameters.
80             my $instance = eval { $registry->process($uri->query_form_hash) };
81              
82             # Contents have already been coerced
83             my $thingy = $instance->get($key);
84              
85             # This will perform type validation and coercion, so if you aren't
86             # certain the input is clean, you'll want to wrap this call in an
87             # eval.
88             eval { $instance->set($key, $val) };
89              
90             # Take a subset of parameters peculiar to a certain application.
91             my $group = $instance->group($name);
92              
93             # This string is guaranteed to be consistent for a given set of
94             # parameters and values.
95             $uri->query($instance->as_string);
96              
97             =head1 METHODS
98              
99             =head2 get $KEY
100              
101             Retrieve an element of the parameter instance.
102              
103             =cut
104              
105             sub get {
106 0     0 1   my ($self, $key) = @_;
107 0           my $content = $self->_content;
108 0 0         return $content->{$key} if exists $content->{$key};
109              
110             # otherwise...
111              
112 0           my $t = $self->registry->template($key);
113 0           my %c = map { $_ => 1 } $self->keys;
  0            
114 0           my $c = scalar grep { $c{$_} } $t->conflicts;
  0            
115              
116 0 0 0       if (!$c and my $d = $t->default) {
117             # call the default with both the template *and* the instance
118 0           my $val = $d->($t, $self);
119              
120             # this is me being clever
121 0           my $c = $t->composite;
122 0 0         if ($c = $c->coercion) {
123 0           return $c->coerce($val);
124             }
125              
126 0           return $val;
127             }
128              
129 0           return;
130             }
131              
132             =head2 set \%PARAMS | $KEY, $VAL [, $KEY2, \@VALS2 ...]
133              
134             Modifies one or more of the parameters in the instance. Attempts to
135             coerce the input according to the template. Accepts, as values, either
136             a literal, an C<ARRAY> reference of literals, or the target datatype.
137             If a <Params::Registry::Template/composite> is specified for a given
138             key, C<ARRAY> references will be coerced into the appropriate
139             composite datatype.
140              
141             Syntax, semantics, cardinality, dependencies and conflicts are all
142             observed, but cascading is I<not>. This method will throw an exception
143             if the input can't be reconciled with the L<Params::Registry> that
144             generated the instance.
145              
146             =cut
147              
148             # it isn't clear why '_process' should not admit already-parsed
149             # values, and why 'set' should not do cascading. they are essentially
150             # identical. in fact, we may be able to just get rid of '_process'
151             # altogether in favour of 'set'.
152              
153             # the difference between 'set' and '_process' is that '_process' runs
154             # defaults while 'set' does not, and 'set' compares depends/conflicts
155             # with existing content while '_process' has nothing to compare it to.
156              
157             # * parameters handed to 'set' may already be parsed, or partially
158             # parsed (as in an arrayref of 'type' but not 'composite')
159              
160             # * dependencies, conflicts, and precursor 'consumes' parameters may
161             # be present in the existing data structure
162              
163             # * dependencies/conflicts can be cleared by passing in 'undef'; to
164             # deal with 'empty' parameters, pass in an empty arrayref or
165             # arrayref containing only undefs.
166              
167             # although if the parameters are ranked and inserted ,
168              
169             sub set {
170 0     0 1   my $self = shift;
171              
172             # deal with parameters and metaparameters
173 0           my (%p, %meta);
174 0 0         if (ref $_[0]) {
175 0 0         Params::Registry::Error->throw
176             ('If the first argument is a ref, it has to be a HASH ref')
177             unless ref $_[0] eq 'HASH';
178             # params are their own hashref
179 0           %p = %{$_[0]};
  0            
180              
181 0 0         if (ref $_[1]) {
182 0 0         Params::Registry::Error->throw
183             ('If the first and second arguments are refs, ' .
184             'they both have to be HASH refs')
185             unless ref $_[1] eq 'HASH';
186              
187             # metaparams are their own hashref
188 0           %meta = %{$_[1]};
  0            
189             }
190             else {
191 0 0         Params::Registry::Error->throw
192             ('Expected even number of args for metaparameters')
193             unless @_ % 2 == 1; # note: even is actually odd here
194              
195             # metaparams are everything after the hashref
196 0           %meta = @_[1..$#_];
197             }
198             }
199             else {
200 0 0         Params::Registry::Error->throw
201             ('Expected even number of args for metaparameters')
202             unless @_ % 2 == 0; # note: even is actually even here
203              
204             # arguments = params
205 0           %p = @_;
206              
207             # pull metaparams out of ordinary params
208 0           %meta = map { $_ => delete $p{$_} } qw(-defaults -force);
  0            
209             }
210              
211             # grab the parent object that stores all the configuration data
212 0           my $r = $self->registry;
213              
214             # create a map of params to complement/negate
215 0           my %neg;
216 0 0         if (my $c = delete $p{$r->complement}) {
217 0           my $x = ref $c;
218 0 0 0       Params::Registry::Error->throw
219             ('If complement is a reference, it must be an ARRAY reference')
220             if $x and $x ne 'ARRAY';
221 0 0         map { $neg{$_} = 1 } @{$x ? $c : [$c]};
  0            
  0            
222             }
223              
224             # and now for the product
225 0           my %out = %{$self->_content};
  0            
226 0           my (%del, %err);
227             # the registry has already ranked groups of parameters by order of
228             # depends/consumes
229 0           for my $list (@{$r->_ranked}) {
  0            
230             # each rank has a list of parameters which are roughly in the
231             # original sequence provided to the registry
232 0           for my $p (@$list) {
233              
234             # normalize input value(s) if present
235 0           my @v;
236 0 0         if (exists $p{$p}) {
237 0           my $v = $p{$p};
238              
239             # XXX i wonder how long it has been the case that you
240             # can't delete a param in `set`
241 0 0         unless (defined $v) {
242 0           $del{$p} = 1;
243 0           next;
244             }
245              
246 0           my $rv = ref $v;
247 0 0 0       $v = [$v] if !$rv || $rv ne 'ARRAY';
248 0           @v = @$v;
249             }
250              
251             # skip if there's nothing to set
252 0 0 0       next if @v == 0 and !$meta{-force};
253              
254             # retrieve the appropriate template object
255 0           my $t = $r->template($p);
256              
257             # run the preprocessor
258 0           my @deps = $t->_consdep;
259 0 0 0       if (my $pp = $t->preproc
260 0           and @deps == grep { exists $out{$_} } @deps) {
261             try {
262             # apply the preprocessor
263 0     0     @v = $pp->($t, \@v, @out{@deps});
264              
265             # get rid of consumed parameters
266 0           map { $del{$_} = 1 } $t->consumes;
  0            
267             } catch {
268 0     0     $err{$p} = $_;
269 0           };
270             }
271              
272             # now we run the main parameter template processor
273 0 0 0       if (!$err{$p} and @v > 0) {
274             try {
275 0     0     my $tmp = $t->process(@v);
276 0 0 0       $out{$p} = $tmp if defined $tmp or $t->empty;
277             } catch {
278 0     0     $err{$p} = $_;
279 0           };
280             }
281              
282             # now we test for conflicts
283 0 0         unless ($err{$p}) {
284 0 0         my @x = grep { $out{$_} && !$del{$_} } $t->conflicts;
  0            
285 0 0         $err{$p} = Params::Registry::Error->new
286             (sprintf '%s conflicts with %s', $p, join ', ', @x) if @x;
287             }
288              
289              
290             # XXX what was the problem with this? 2016-05-30
291              
292             # elsif ($meta{-defaults} and my $d = $t->default) {
293             # # add a default value unless there are conflicts
294             # my @x = grep { $out{$_} && !$del{$_} } $t->conflicts;
295             # $out{$p} = $d->($t) unless @x;
296             # }
297              
298             # now handle the complement
299 0 0 0       if (!$err{$p} and $neg{$p} and $t->has_complement) {
      0        
300 0           $out{$p} = $t->complement($out{$p});
301             }
302             }
303             }
304              
305 0 0         Params::Registry::Error::Processing->throw(parameters => \%err)
306             if keys %err;
307              
308             # we waited to delete the contents all at once in case there were
309             # dependencies
310 0           map { delete $out{$_} } keys %del;
  0            
311              
312             # now we replace the content all in one shot
313 0           %{$self->_content} = %out;
  0            
314              
315             # not sure what else to return
316 0           return $self;
317             }
318              
319             =head2 group $KEY
320              
321             Selects a subset of the instance according to the groups laid out in
322             the L<Params::Registry> specification, clones them, and returns them
323             in a C<HASH> reference, suitable for passing into another method.
324              
325             =cut
326              
327             sub group {
328 0     0 1   my ($self, $key) = @_;
329              
330 0           my %out;
331 0 0         my @list = @{$self->registry->_groups->{$key} || []};
  0            
332 0           my $c = $self->_content;
333 0           for my $k (@list) {
334             # XXX ACTUALLY CLONE THESE (MAYBE)
335              
336             # use exists, not defined
337 0 0         $out{$k} = $c->{$k} if exists $c->{$k};
338             }
339              
340 0           \%out;
341             }
342              
343             =head2 template $KEY
344              
345             Retrieves the template for C<$KEY>.
346              
347             =cut
348              
349             sub template {
350 0     0 1   my ($self, $key) = @_;
351 0           $self->registry->template($key);
352             }
353              
354             =head2 clone $KEY => $VAL [...] | \%PAIRS
355              
356             Produces a clone of the instance object, with the supplied parameters
357             overwritten. Internally, this uses L</set>, so the input must already
358             be clean, or wrapped in an C<eval>.
359              
360             =cut
361              
362             sub clone {
363 0     0 1   my $self = shift;
364 0 0         my %p = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  0            
365              
366              
367             # XXX deep copy?
368 0           my %orig = %{$self->_content};
  0            
369              
370             # sometimes we only want to clone certain params
371 0 0         if (defined $p{-only}) {
372 0           my $o = delete $p{-only};
373 0 0         my %only = map { $_ => 1 } (ref $o ? @$o : $o);
  0            
374 0 0         map { delete $orig{$_} unless $only{$_} } keys %orig if %only;
  0 0          
375             }
376              
377 0           my $out = Params::Registry::Instance->new(
378             registry => $self->registry,
379             _content => \%orig,
380             );
381              
382              
383 0 0         $out->set(\%p) if keys %p;
384              
385             # warn 'waaaat ' . $out->as_string;
386              
387 0           $out;
388             }
389              
390             =head2 as_hash
391              
392             Dump out a hash(ref) of the parameter instance, depending on the
393             calling context.
394              
395             =cut
396              
397             sub as_hash {
398 0     0 1   my %out = %{$_[0]->_content};
  0            
399 0 0         wantarray ? %out : \%out;
400             }
401              
402             =head2 as_where_clause
403              
404             Generates a data structure suitable to pass into L<SQL::Abstract>
405             (e.g., via L<DBIx::Class>).
406              
407             =cut
408              
409             sub _do_span {
410 0     0     my ($span, $universe) = @_;
411              
412 0           my ($s, $e) = ($span->start, $span->end);
413              
414             # deal with possibly-empty universe
415 0           my ($us, $ue);
416 0 0         if ($universe) {
417 0 0         my $u = $universe->isa('DateTime::SpanSet')
418             ? $universe->span : $universe;
419 0           ($us, $ue) = ($u->start, $u->end);
420             }
421              
422             # adjust for open sets
423 0 0         my $sop = $span->start_is_open ? '>' : '>=';
424 0 0         my $eop = $span->end_is_open ? '<' : '<=';
425              
426             # XXX this does not adjust for BETWEEN or when start and end are
427             # the same
428              
429 0           my %out;
430 0 0 0       if ($s->is_finite and (!$us or $s > $us)) {
      0        
431 0           $out{$sop} = $s;
432             }
433 0 0 0       if ($e->is_finite and (!$ue or $e < $ue)) {
      0        
434 0           $out{$eop} = $e;
435             }
436              
437             # this can be empty and that screws up sql generation
438 0 0         return \%out if keys %out;
439             }
440              
441             # XXX these should really be embedded in the types, no?
442              
443             # NOTE: we have these functions return the key along with the clause
444             # to signal that there actually *is* a a clause, because just
445             # returning undef could be interpreted by SQL::Abstract as IS NULL,
446             # and we don't want that. Unless we actually *do* want that.
447              
448             my %TYPES = (
449             'Set::Scalar' => sub {
450             # any set coming into this sub will already have been complemented
451             my ($key, $val, $template) = @_;
452              
453             # there is nothing to select
454             my $vs = $val->size;
455             return if $vs == 0;
456              
457             # there is everything to select
458             my $comp = $template->complement($val);
459             my $cs = $comp->size;
460             return if $cs == 0;
461              
462             if ($vs > $cs) {
463             my @e = $comp->elements;
464             return ($key => $cs == 1 ? { '!=' => $e[0] } : { -not_in => \@e });
465             }
466             else {
467             my @e = $val->elements;
468             return ($key => $vs == 1 ? $e[0] : { -in => \@e });
469             }
470             },
471             'Set::Infinite' => sub {
472             my ($key, $val, $template) = @_;
473             return if $val->is_empty;
474              
475             # bail out if the span is wider than the universe
476             my $universe = $template->universe;
477             return if $universe and $val->is_span
478             and $val->min <= $universe->min and $val->max >= $universe->max;
479              
480             my $inf = Set::Infinite->inf;
481             my $ninf = Set::Infinite->minus_inf;
482              
483             my @ranges;
484             my ($span, $tail) = $val->first;
485             do {
486             my ($min, $mop) = $span->min_a;
487             my ($max, $xop) = $span->max_a;
488              
489             my $closed = !($mop || $xop);
490             $mop = $mop ? '>' : '>=';
491             $xop = $xop ? '<' : '<=';
492              
493             my %rec;
494             if ($min == $ninf and $max == $inf) {
495             next;
496             }
497             elsif ($closed and $min > $ninf and $max < $inf) {
498             if ($min == $max) {
499             push @ranges, $min;
500             }
501             else {
502             $rec{-between} = [$min, $max];
503             }
504             }
505             else {
506             $rec{$mop} = $min + 0 unless $min == $ninf;
507             $rec{$xop} = $max + 0 unless $max == $inf;
508             }
509              
510             push @ranges, \%rec if keys %rec;
511              
512             ($span, $tail) = $tail ? $tail->first : ();
513             } while ($span);
514              
515             return ($key, $ranges[0]) if @ranges == 1;
516             return ($key, \@ranges) if @ranges;
517             },
518             'DateTime::Span' => sub {
519             my ($key, $val, $template) = @_;
520              
521             my $out = _do_span($val, $template->universe);
522             return ($key, $out) if $out;
523             },
524             'DateTime::SpanSet' => sub {
525             my ($key, $val, $template) = @_;
526             my $u = $template->universe;
527              
528             my @spans;
529             for my $span ($val->as_list) {
530             my $rule = _do_span($span, $u);
531             push @spans, $rule if $rule;
532             }
533             return ($key, $spans[0]) if @spans == 1;
534             return ($key, \@spans) if @spans;
535             },
536             # i don't think we have any of these at the moment
537             'DateTime::Set' => sub {
538             },
539             'ARRAY' => sub {
540             my ($key, $val) = @_;
541             return ($key, { -in => [ @$val ] });
542             },
543             );
544              
545             sub as_where_clause {
546 0     0 1   my $self = shift;
547 0           my %p = @_;
548              
549 0 0         my %only = map { $_ => 1 } @{$p{only} || []};
  0            
  0            
550              
551 0           my %out;
552              
553 0           my $r = $self->registry;
554              
555 0           for my $kin ($self->keys) {
556             # skip skeep skorrp
557 0 0 0       next if %only && !$only{$kin};
558              
559 0           my $vin = $self->get($kin);
560              
561 0           my $dispatch;
562 0 0         if (my $ref = ref $vin) {
563 0 0         unless ($dispatch = $TYPES{$ref}) {
564 0 0         if (Scalar::Util::blessed($vin)) {
565 0           for my $t (keys %TYPES) {
566 0 0         if ($vin->isa($t)) {
567 0           $dispatch = $TYPES{$t};
568 0           last;
569             }
570             }
571             }
572             }
573             }
574              
575 0           my ($kout, $vout);
576 0 0         if ($dispatch) {
577 0           my $t = $r->template($kin);
578 0           ($kout, $vout) = $dispatch->($kin, $vin, $t);
579             }
580             else {
581 0           ($kout, $vout) = ($kin, $vin);
582             }
583              
584 0 0         $out{$kout} = $vout if $kout;
585             }
586              
587 0 0         wantarray ? %out : \%out;
588             }
589              
590             =head2 as_string
591              
592             Generates the canonical URI query string according to the template.
593              
594             =cut
595              
596             sub as_string {
597 0     0 1   my $self = shift;
598 0           my $r = $self->registry;
599              
600             # this just creates [key => \@values], ...
601 0           my (@out, %comp);
602 0           for my $k ($r->sequence) {
603             # skip unless the parameter is present. this gets around
604             # 'empty'-marked params that we don't actually have.
605 0 0         next unless $self->exists($k);
606              
607 0           my $t = $r->template($k);
608 0           my $v = $self->get($k);
609              
610             # get dependencies
611 0           my @dep = map { $self->get($_) } $t->depends;
  0            
612              
613             # retrieve un-processed ARRAY ref
614 0           (my $obj, $comp{$k}) = $t->unprocess($v, @dep);
615              
616             # skip empties
617 0 0         next unless defined $obj;
618              
619             # in general we want to be conservative about what we escape:
620             # control characters, space
621              
622             # accumulate
623 0           push @out, [$k, $obj];
624             }
625              
626             # XXX we have to handle complements here
627              
628             # for sets/composites, check if displaying '&complement=key' is
629             # shorter than just displaying the contents of the set
630             # (e.g. &key=val&key=val&key=val... it almost certainly will be).
631              
632             # XXX we *also* need to handle escaping
633              
634             return join '&', map {
635 0           my $x = $_->[0];
  0            
636             map {
637 0           sprintf '%s=%s', $x, URI::Escape::uri_escape_utf8
638 0           ($_, q{^-._~0-9A-Za-z\Q:/[]@!\$'()*+,;\E}) } @{$_->[1]} } @out;
  0            
639             }
640              
641             =head2 make_uri $URI
642              
643             Accepts a L<URI> object and returns a clone of that object with its
644             query string overwritten with the contents of the instance. This is a
645             convenience method for idioms like:
646              
647             my $new_uri = $instance->clone(foo => undef)->make_uri($old_uri);
648              
649             As expected, this will produce a new instance with the C<foo>
650             parameter removed, which is then used to generate a URI, suitable for
651             a link.
652              
653             =cut
654              
655             sub make_uri {
656 0     0 1   my ($self, $uri) = @_;
657             # looks like URI.pm has made canonical no longer always clone
658 0           $uri = $uri->clone->canonical;
659 0           my $q = $self->as_string;
660 0 0 0       undef $q if defined $q and $q eq '';
661 0           $uri->query($q);
662 0           $uri;
663             }
664              
665             =head1 AUTHOR
666              
667             Dorian Taylor, C<< <dorian at cpan.org> >>
668              
669             =head1 SEE ALSO
670              
671             =over 4
672              
673             =item
674              
675             L<Params::Registry>
676              
677             =item
678              
679             L<Params::Registry::Template>
680              
681             =back
682              
683             =head1 LICENSE AND COPYRIGHT
684              
685             Copyright 2013 Dorian Taylor.
686              
687             Licensed under the Apache License, Version 2.0 (the "License"); you
688             may not use this file except in compliance with the License. You may
689             obtain a copy of the License at
690             L<http://www.apache.org/licenses/LICENSE-2.0> .
691              
692             Unless required by applicable law or agreed to in writing, software
693             distributed under the License is distributed on an "AS IS" BASIS,
694             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
695             implied. See the License for the specific language governing
696             permissions and limitations under the License.
697              
698             =cut
699              
700             __PACKAGE__->meta->make_immutable;
701              
702             1; # End of Params::Registry::Instance