File Coverage

blib/lib/Catalyst/Utils/StructuredParameters.pm
Criterion Covered Total %
statement 150 162 92.5
branch 66 88 75.0
condition 36 47 76.6
subroutine 17 19 89.4
pod 0 8 0.0
total 269 324 83.0


line stmt bran cond sub pod time code
1              
2             use Moose;
3 2     2   14 use Storable qw(dclone);
  2         5  
  2         14  
4 2     2   11321 use Catalyst::Utils;
  2         4  
  2         161  
5 2     2   13 use Catalyst::Exception::MissingParameter;
  2         4  
  2         62  
6 2     2   766 use Catalyst::Exception::InvalidArrayPointer;
  2         616  
  2         77  
7 2     2   987 use Catalyst::Exception::InvalidArrayLength;
  2         569  
  2         55  
8 2     2   747  
  2         620  
  2         3237  
9             our $MAX_ARRAY_DEPTH = 1000;
10              
11             has context => (is=>'ro', required=>1);
12             has _namespace => (is=>'rw', required=>0, isa=>'ArrayRef', predicate=>'has_namespace', init_arg=>'namespace');
13             has _flatten_array_value => (is=>'rw', required=>1, init_arg=>'flatten_array_value');
14             has _current => (is=>'rw', required=>0, init_arg=>undef);
15             has _required => (is=>'rw', required=>0, init_arg=>undef);
16             has _src => (is=>'ro', required=>1, init_arg=>'src');
17             has _max_array_depth => (is=>'ro', required=>1, init_arg=>'max_array_depth', default=>$MAX_ARRAY_DEPTH);
18              
19             my ($self, $arg) = @_;
20             $self->_namespace($arg) if defined($arg);
21 2     2 0 11 return $self;
22 2 50       68 }
23 2         46  
24             my ($self, $arg) = @_;
25             $self->_flatten_array_value($arg) if defined($arg);
26             return $self;
27 0     0 0 0 }
28 0 0       0  
29 0         0 my ($self, $arg) = @_;
30             $self->_max_array_depth($arg) if defined($arg);
31             return $self;
32             }
33 37     37 0 63  
34 37 50       79 my ($self, @proto) = @_;
35 37         95 my $namespace = $self->_namespace ||[];
36             $self->_required(0);
37              
38             if(ref $proto[0]) {
39 13     13 0 80 my $namespace_affix = shift @proto;
40 13   100     460 $namespace = [ @$namespace, @$namespace_affix ];
41 13         433 }
42              
43 13 100       38 my $context = dclone($self->context);
44 7         22 my $parsed = $self->_parse($context, $namespace, [@proto]);
45 7         26 my $current = $self->_current ||+{};
46             $current = Catalyst::Utils::merge_hashes($current, $parsed);
47             $self->_current($current);
48 13         415  
49 13         90 return $self;
50 13   100     447 }
51 13         66  
52 13         733 my ($self, @proto) = @_;
53             my $namespace = $self->_namespace ||[];
54 13         104 $self->_required(1);
55              
56             if(ref $proto[0]) {
57             my $namespace_affix = shift @proto;
58 4     4 0 11 $namespace = [ @$namespace, @$namespace_affix ];
59 4   50     133 }
60 4         122  
61             my $context = dclone($self->context);
62 4 50       12 my $parsed = $self->_parse($context, $namespace, [@proto]);
63 0         0 my $current = $self->_current ||+{};
64 0         0 $current = Catalyst::Utils::merge_hashes($current, $parsed);
65             $self->_current($current);
66              
67 4         116 return $self;
68 4         20 }
69 2   50     62  
70 2         10 my $self = shift;
71 2         93 return %{ $self->_current || +{} };
72             }
73 2         10  
74             my $self = shift;
75             return CORE::keys %{ $self->_current || +{} };
76             }
77 9     9 0 29  
78 9 50       15 my $self = shift;
  9         268  
79             return @{ $self->_current || +{} }{@_};
80             }
81              
82 0     0 0 0 return 1 if $a eq '';
83 0 0       0 return -1 if $b eq '';
  0         0  
84             return $a <=> $b;
85             }
86              
87 1     1 0 294 my ($self, $value) = @_;
88 1 50       3 return $value unless $self->_flatten_array_value;
  1         31  
89             return ((ref($value)||'') eq 'ARRAY') ? $value->[-1] : $value;
90             }
91              
92 32 100   32   74 my ($self, @args) = @_;
93 29 100       62 return $self->_src eq 'data' ? $self->_parse_data(@args) : $self->_parse_formlike(@args);
94 22         64 }
95              
96             my ($self, $context, $ns, $rules) = @_;
97              
98 121     121   211 my $current = +{};
99 121 100       3884 while(@{$rules}) {
100 63 100 100     303 my $rule = shift @{$rules};
101             if(ref($rule)||'' eq 'HASH') {
102             my ($local_ns, $rules) = %$rule;
103             my $key = join('.', @$ns, $local_ns);
104 17     17   50 my %indexes = ();
105 17 100       599 foreach my $context_field (CORE::keys %$context) {
106             my ($i, $under) = ($context_field =~m/^\Q$key\E\[(\d*)\]\.?(.*)$/);
107             next unless defined $i;
108             $indexes{$i} = $under;
109 71     71   147 }
110              
111 71         111 my $found_array_depth = scalar CORE::keys %indexes;
112 71         133 Catalyst::Exception::InvalidArrayLength->throw(
  216         405  
113 147         200 pointer=>$local_ns,
  147         259  
114 147 100 100     448 max=>$self->max_array_depth,
115 27         87 attempted=>$found_array_depth
116 27         79 ) if $found_array_depth > $self->max_array_depth;
117 27         49  
118 27         176 foreach my $index(sort _sorted CORE::keys %indexes) {
119 717         2208 my $cloned_rules = dclone($rules); # each iteration in the loop needs its own copy of the rules;
120 717 100       1402 $cloned_rules = [''] unless @$cloned_rules; # to handle the bare array case
121 72         169 my $old = $self->_flatten_array_value;
122             $self->_flatten_array_value(0) if $index eq '';
123             my $value = $self->_parse_formlike( $context, [@$ns, "${local_ns}[$index]"], $cloned_rules);
124 27         76 if(($index eq '') && $old) {
125 27 50       93 $self->_flatten_array_value($old);
126             if( (ref($value)||'') eq 'ARRAY') {
127             push @{$current->{$local_ns}}, $_ for @$value;
128             } elsif((ref($value)||'') eq 'HASH') {
129             if(ref $value->{$indexes{$index}}) {
130             my @values = map { +{ $indexes{$index} => $_ } } @{ $value->{$indexes{$index}} };
131 27         144 push @{$current->{$local_ns}}, @values;
132 36         717 } else {
133 36 100       116 push @{$current->{$local_ns}}, $value;
134 36         1294 }
135 36 100       254 }
136 36         157 } else {
137 36 100 66     112 ## I don't think these are missing params, just a row with invalid fields
138 6         196 next if( (ref($value)||'') eq 'HASH') && !%$value;
139 6 100 50     63 push @{$current->{$local_ns}}, $value;
    50 50        
140 1         4 }
  2         8  
141             }
142 5 100       26 } else {
143 2         7 if((ref($rules->[0])||'') eq 'ARRAY') {
  5         19  
  2         11  
144 2         5 my $value = $self->_parse_formlike( $context, [@$ns, $rule], shift(@$rules) );
  2         14  
145             next unless %$value; # For 'permitted';
146 3         6 $current->{$rule} = $value;
  3         75  
147             } else {
148             if($rule eq '') {
149             my $key = join('.', @$ns);
150             unless(defined $context->{$key}) {
151 30 100 100     125 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>$key) : next;
      100        
152 29         38 }
  29         95  
153             $current = $self->_normalize_array_value($context->{$key});
154             } else {
155             my $key = join('.', @$ns, $rule);
156 120 100 100     328 unless(defined $context->{$key}) {
157 20         91 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>$key) : next;
158 20 100       54 }
159 13         40 $current->{$rule} = $self->_normalize_array_value($context->{$key});
160             }
161 100 100       155 }
162 15         40 }
163 15 50       49 }
164 0 0       0 return $current;
165             }
166 15         40  
167             my ($self, $context, $ns, $rules) = @_;
168 85         190 my $current = +{};
169 85 100       201 MAIN: while(@{$rules}) {
170 29 100       868 my $rule = shift @{$rules};
171             if(ref($rule)||'' eq 'HASH') {
172 56         125 my ($local_ns, $rules) = %$rule;
173             my $value = $context;
174             foreach my $pointer (@$ns, $local_ns) {
175             if(exists($value->{$pointer})) {
176             $value = $value->{$pointer};
177 69         135 } else {
178             $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $local_ns))) : next MAIN;
179             }
180             }
181 38     38   104  
182 38         59 Catalyst::Exception::InvalidArrayPointer->throw(pointer=>join('.', (@$ns, $local_ns))) unless (ref($value)||'') eq 'ARRAY';
183 38         58  
  136         273  
184 98         136 my $found_array_depth = scalar @$value;
  98         148  
185 98 100 100     255 Catalyst::Exception::InvalidArrayLength->throw(
186 20         56 pointer=>$local_ns,
187 20         35 max=>$self->max_array_depth,
188 20         39 attempted=>$found_array_depth
189 30 100       46 ) if $found_array_depth > $self->max_array_depth;
190 20         37
191             my @gathered = ();
192 10 50       301  
193             foreach my $item (@$value) {
194             my $cloned_rules = dclone($rules); # each iteration in the loop needs its own copy of the rules;
195             $cloned_rules = [''] unless @$cloned_rules; # to handle the bare array case
196 10 50 50     64 my $value = $self->_parse_data($item, [], $cloned_rules);
197             ## I don't think these are missing params, just a row with invalid fields
198 10         19 next if( (ref($value)||'') eq 'HASH') && !%$value;
199 10 50       30 push @gathered, $value;
200             }
201             $current->{$local_ns} = \@gathered;
202             } else {
203             if((ref($rules->[0])||'') eq 'ARRAY') {
204             my $value = $self->_parse_data( $context, [@$ns, $rule], shift(@$rules) );
205 10         17 next unless %$value; # For 'permitted';
206             $current->{$rule} = $value;
207 10         22 } else {
208 25         405 if($rule eq '') {
209 25 100       78 my $value = $context;
210 25         62 foreach my $pointer (@$ns) {
211             if(((ref($value)||'') eq 'HASH') && exists($value->{$pointer})) {
212 25 100 100     92 $value = $value->{$pointer};
      100        
213 24         58 } else {
214             $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $rule))) : next MAIN;
215 10         42 }
216             }
217 78 100 100     201 $current = $self->_normalize_array_value($value);
218 11         59 } else {
219 11 100       32 my $value = $context;
220 6         14 foreach my $pointer (@$ns, $rule) {
221             if(((ref($value)||'') eq 'HASH') && exists($value->{$pointer})) {
222 67 100       111 $value = $value->{$pointer};
223 14         21 } else {
224 14         27 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $rule))) : next MAIN;
225 0 0 0     0 }
      0        
226 0         0 }
227             $current->{$rule} = $self->_normalize_array_value($value);
228 0 0       0 }
229             }
230             }
231 14         34 }
232             return $current;
233 53         64 }
234 53         96  
235 91 100 100     279  
      100        
236 74         130 __PACKAGE__->meta->make_immutable;
237              
238 17 50       508 1;
239              
240             =head1 NAME
241 36         74  
242             Catalyst::Utils::StructuredParameters - Enforce structural rules on your body and data parameters
243              
244             =head1 SYNOPSIS
245              
246 38         77 =head1 DESCRIPTION
247              
248             See L<Catalyst::TraitFor::Request::StructuredParameters> for usage. These are just utility classes
249             and not likely useful for end user unless you are rolling your own parsing or something. All
250             the publically useful docs are there.
251              
252             =head1 ATTRIBUTES
253              
254             This role defines the following attributes:
255              
256             TBD
257              
258             =head1 METHODS
259              
260             This role defines the following methods:
261              
262             TBD
263              
264             =head1 AUTHOR
265              
266             See L<Catalyst::TraitFor::Request::StructuredParameters>
267              
268             =head1 SEE ALSO
269              
270             L<Catalyst>, L<Catalyst::Request>
271              
272             =head1 COPYRIGHT & LICENSE
273              
274             See L<Catalyst::TraitFor::Request::StructuredParameters>
275              
276             =cut