File Coverage

blib/lib/Catalyst/Utils/StrongParameters.pm
Criterion Covered Total %
statement 122 131 93.1
branch 53 68 77.9
condition 32 40 80.0
subroutine 14 15 93.3
pod 0 5 0.0
total 221 259 85.3


line stmt bran cond sub pod time code
1             package Catalyst::Utils::StrongParameters;
2              
3 2     2   32 use Moose;
  2         5  
  2         16  
4 2     2   13977 use Storable qw(dclone);
  2         6  
  2         199  
5 2     2   15 use Catalyst::Utils;
  2         5  
  2         72  
6 2     2   1013 use Catalyst::Exception::MissingParameter;
  2         755  
  2         96  
7 2     2   1289 use Catalyst::Exception::InvalidArrayPointer;
  2         761  
  2         3405  
8              
9             has context => (is=>'ro', required=>1);
10             has _namespace => (is=>'rw', required=>0, isa=>'ArrayRef', predicate=>'has_namespace', init_arg=>'namespace');
11             has _flatten_array_value => (is=>'ro', required=>1, init_arg=>'flatten_array_value');
12             has _current => (is=>'rw', required=>0, init_arg=>undef);
13             has _required => (is=>'rw', required=>0, init_arg=>undef);
14             has _src => (is=>'ro', required=>1, init_arg=>'src');
15              
16             sub namespace {
17 2     2 0 7 my ($self, $arg) = @_;
18 2 50       77 $self->_namespace($arg) if defined($arg);
19 2         29 return $self;
20             }
21             sub flatten_array_value {
22 0     0 0 0 my ($self, $arg) = @_;
23 0 0       0 $self->_flatten_array_value($arg) if defined($arg);
24 0         0 return $self;
25             }
26              
27             sub permitted {
28 10     10 0 39 my ($self, @proto) = @_;
29 10   100     444 my $namespace = $self->_namespace ||[];
30 10         405 $self->_required(0);
31              
32 10 100       34 if(ref $proto[0]) {
33 4         12 my $namespace_affix = shift @proto;
34 4         15 $namespace = [ @$namespace, @$namespace_affix ];
35             }
36              
37 10         353 my $context = dclone($self->context);
38 10         73 my $parsed = $self->_parse($context, $namespace, [@proto]);
39 10   100     372 my $current = $self->_current ||+{};
40 10         47 $current = Catalyst::Utils::merge_hashes($current, $parsed);
41 10         665 $self->_current($current);
42              
43 10         114 return $self;
44             }
45              
46             sub required {
47 4     4 0 17 my ($self, @proto) = @_;
48 4   50     143 my $namespace = $self->_namespace ||[];
49 4         152 $self->_required(1);
50              
51 4 50       14 if(ref $proto[0]) {
52 0         0 my $namespace_affix = shift @proto;
53 0         0 $namespace = [ @$namespace, @$namespace_affix ];
54             }
55              
56 4         134 my $context = dclone($self->context);
57 4         25 my $parsed = $self->_parse($context, $namespace, [@proto]);
58 2   50     78 my $current = $self->_current ||+{};
59 2         10 $current = Catalyst::Utils::merge_hashes($current, $parsed);
60 2         125 $self->_current($current);
61              
62 2         12 return $self;
63             }
64              
65             sub to_hash {
66 6     6 0 16 my $self = shift;
67 6 50       10 return %{ $self->_current || +{} };
  6         208  
68             }
69              
70             sub _sorted {
71 24 100   24   58 return 1 if $a eq '';
72 22 100       41 return -1 if $b eq '';
73 21         57 return $a <=> $b;
74             }
75              
76             sub _normalize_array_value {
77 112     112   233 my ($self, $value) = @_;
78 112 100       4316 return $value unless $self->_flatten_array_value;
79 62 100 100     334 return ((ref($value)||'') eq 'ARRAY') ? $value->[-1] : $value;
80             }
81              
82             sub _parse {
83 14     14   43 my ($self, @args) = @_;
84 14 100       579 return $self->_src eq 'data' ? $self->_parse_data(@args) : $self->_parse_formlike(@args);
85             }
86              
87             sub _parse_formlike {
88 56     56   130 my ($self, $context, $ns, $rules) = @_;
89 56         100 my $current = +{};
90 56         102 while(@{$rules}) {
  184         416  
91 130         324 my $rule = shift @{$rules};
  130         242  
92 130 100 100     424 if(ref($rule)||'' eq 'HASH') {
93 24         82 my ($local_ns, $rules) = %$rule;
94 24         69 my $key = join('.', @$ns, $local_ns);
95 24         45 my %indexes = ();
96 24         191 foreach my $context_field (keys %$context) {
97 698         2565 my ($i, $under) = ($context_field =~m/^\Q$key\E\[(\d*)\]\.?(.*)$/);
98 698 100       1586 next unless defined $i;
99 62         160 $indexes{$i} = $under;
100             }
101 24         165 foreach my $index(sort _sorted keys %indexes) {
102 27         708 my $cloned_rules = dclone($rules); # each iteration in the loop needs its own copy of the rules;
103 27 100       100 $cloned_rules = [''] unless @$cloned_rules; # to handle the bare array case
104 27         149 my $value = $self->_parse_formlike( $context, [@$ns, "${local_ns}[$index]"], $cloned_rules);
105             ## I don't think these are missing params, just a row with invalid fields
106 27 100 100     138 next if( (ref($value)||'') eq 'HASH') && !%$value;
      100        
107 26         47 push @{$current->{$local_ns}}, $value;
  26         121  
108             }
109             } else {
110 106 100 100     331 if((ref($rules->[0])||'') eq 'ARRAY') {
111 17         82 my $value = $self->_parse_formlike( $context, [@$ns, $rule], shift(@$rules) );
112 17 100       59 next unless %$value; # For 'permitted';
113 10         28 $current->{$rule} = $value;
114             } else {
115 89 100       166 if($rule eq '') {
116 14         46 my $key = join('.', @$ns);
117 14 50       51 unless(defined $context->{$key}) {
118 0 0       0 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>$key) : next;
119             }
120 14         42 $current = $self->_normalize_array_value($context->{$key});
121             } else {
122 75         196 my $key = join('.', @$ns, $rule);
123 75 100       206 unless(defined $context->{$key}) {
124 27 100       974 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>$key) : next;
125             }
126 48         128 $current->{$rule} = $self->_normalize_array_value($context->{$key});
127             }
128             }
129             }
130             }
131 54         140 return $current;
132             }
133              
134             sub _parse_data {
135 38     38   89 my ($self, $context, $ns, $rules) = @_;
136 38         67 my $current = +{};
137 38         65 MAIN: while(@{$rules}) {
  136         329  
138 98         153 my $rule = shift @{$rules};
  98         175  
139 98 100 100     302 if(ref($rule)||'' eq 'HASH') {
140 20         67 my ($local_ns, $rules) = %$rule;
141 20         39 my $value = $context;
142 20         40 foreach my $pointer (@$ns, $local_ns) {
143 30 100       63 if(exists($value->{$pointer})) {
144 20         38 $value = $value->{$pointer};
145             } else {
146 10 50       366 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $local_ns))) : next MAIN;
147             }
148             }
149              
150 10 50 50     35 Catalyst::Exception::InvalidArrayPointer->throw(pointer=>join('.', (@$ns, $local_ns))) unless (ref($value)||'') eq 'ARRAY';
151 10         20 my @gathered = ();
152 10         21 foreach my $item (@$value) {
153 25         539 my $cloned_rules = dclone($rules); # each iteration in the loop needs its own copy of the rules;
154 25 100       94 $cloned_rules = [''] unless @$cloned_rules; # to handle the bare array case
155 25         75 my $value = $self->_parse_data($item, [], $cloned_rules);
156             ## I don't think these are missing params, just a row with invalid fields
157 25 100 100     116 next if( (ref($value)||'') eq 'HASH') && !%$value;
      100        
158 24         65 push @gathered, $value;
159             }
160 10         32 $current->{$local_ns} = \@gathered;
161             } else {
162 78 100 100     243 if((ref($rules->[0])||'') eq 'ARRAY') {
163 11         53 my $value = $self->_parse_data( $context, [@$ns, $rule], shift(@$rules) );
164 11 100       38 next unless %$value; # For 'permitted';
165 6         16 $current->{$rule} = $value;
166             } else {
167 67 100       149 if($rule eq '') {
168 14         25 my $value = $context;
169 14         32 foreach my $pointer (@$ns) {
170 0 0 0     0 if(((ref($value)||'') eq 'HASH') && exists($value->{$pointer})) {
      0        
171 0         0 $value = $value->{$pointer};
172             } else {
173 0 0       0 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $rule))) : next MAIN;
174             }
175             }
176 14         30 $current = $self->_normalize_array_value($value);
177             } else {
178 53         84 my $value = $context;
179 53         112 foreach my $pointer (@$ns, $rule) {
180 91 100 100     324 if(((ref($value)||'') eq 'HASH') && exists($value->{$pointer})) {
      100        
181 74         155 $value = $value->{$pointer};
182             } else {
183 17 50       659 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $rule))) : next MAIN;
184             }
185             }
186 36         80 $current->{$rule} = $self->_normalize_array_value($value);
187             }
188             }
189             }
190             }
191 38         83 return $current;
192             }
193              
194              
195             __PACKAGE__->meta->make_immutable;
196              
197             1;
198              
199             =head1 NAME
200              
201             Catalyst::Utils::StrongParameters - Enforce structural rules on your body and data parameters
202              
203             =head1 SYNOPSIS
204              
205             =head1 DESCRIPTION
206              
207             See L<Catalyst::TraitFor::Request::StrongParameters> for usage. These are just utility classes
208             and not likely useful for end user unless you are rolling your own parsing or something. All
209             the publically useful docs are there.
210              
211             =head1 ATTRIBUTES
212              
213             This role defines the following attributes:
214              
215             TBD
216              
217             =head1 METHODS
218              
219             This role defines the following methods:
220              
221             TBD
222              
223             =head1 AUTHOR
224              
225             See L<Catalyst::TraitFor::Request::StrongParameters>
226              
227             =head1 SEE ALSO
228              
229             L<Catalyst>, L<Catalyst::Request>
230              
231             =head1 COPYRIGHT & LICENSE
232              
233             See L<Catalyst::TraitFor::Request::StrongParameters>
234              
235             =cut