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   18 use Moose;
  2         5  
  2         18  
4 2     2   14352 use Storable qw(dclone);
  2         6  
  2         201  
5 2     2   17 use Catalyst::Utils;
  2         6  
  2         68  
6 2     2   1171 use Catalyst::Exception::MissingParameter;
  2         785  
  2         106  
7 2     2   1184 use Catalyst::Exception::InvalidArrayPointer;
  2         802  
  2         3337  
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 8 my ($self, $arg) = @_;
18 2 50       79 $self->_namespace($arg) if defined($arg);
19 2         34 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 42 my ($self, @proto) = @_;
29 10   100     468 my $namespace = $self->_namespace ||[];
30 10         382 $self->_required(0);
31              
32 10 100       35 if(ref $proto[0]) {
33 4         8 my $namespace_affix = shift @proto;
34 4         17 $namespace = [ @$namespace, @$namespace_affix ];
35             }
36              
37 10         364 my $context = dclone($self->context);
38 10         80 my $parsed = $self->_parse($context, $namespace, [@proto]);
39 10   100     376 my $current = $self->_current ||+{};
40 10         54 $current = Catalyst::Utils::merge_hashes($current, $parsed);
41 10         709 $self->_current($current);
42              
43 10         110 return $self;
44             }
45              
46             sub required {
47 4     4 0 15 my ($self, @proto) = @_;
48 4   50     142 my $namespace = $self->_namespace ||[];
49 4         142 $self->_required(1);
50              
51 4 50       15 if(ref $proto[0]) {
52 0         0 my $namespace_affix = shift @proto;
53 0         0 $namespace = [ @$namespace, @$namespace_affix ];
54             }
55              
56 4         135 my $context = dclone($self->context);
57 4         25 my $parsed = $self->_parse($context, $namespace, [@proto]);
58 2   50     71 my $current = $self->_current ||+{};
59 2         11 $current = Catalyst::Utils::merge_hashes($current, $parsed);
60 2         143 $self->_current($current);
61              
62 2         14 return $self;
63             }
64              
65             sub to_hash {
66 6     6 0 17 my $self = shift;
67 6 50       12 return %{ $self->_current || +{} };
  6         212  
68             }
69              
70             sub _sorted {
71 25 100   25   57 return 1 if $a eq '';
72 21 100       48 return -1 if $b eq '';
73 18         48 return $a <=> $b;
74             }
75              
76             sub _normalize_array_value {
77 112     112   240 my ($self, $value) = @_;
78 112 100       4328 return $value unless $self->_flatten_array_value;
79 62 100 100     341 return ((ref($value)||'') eq 'ARRAY') ? $value->[-1] : $value;
80             }
81              
82             sub _parse {
83 14     14   47 my ($self, @args) = @_;
84 14 100       564 return $self->_src eq 'data' ? $self->_parse_data(@args) : $self->_parse_formlike(@args);
85             }
86              
87             sub _parse_formlike {
88 56     56   142 my ($self, $context, $ns, $rules) = @_;
89 56         111 my $current = +{};
90 56         107 while(@{$rules}) {
  184         444  
91 130         237 my $rule = shift @{$rules};
  130         249  
92 130 100 100     427 if(ref($rule)||'' eq 'HASH') {
93 24         79 my ($local_ns, $rules) = %$rule;
94 24         70 my $key = join('.', @$ns, $local_ns);
95 24         44 my %indexes = ();
96 24         198 foreach my $context_field (keys %$context) {
97 698         2440 my ($i, $under) = ($context_field =~m/^\Q$key\E\[(\d*)\]\.?(.*)$/);
98 698 100       1684 next unless defined $i;
99 62         161 $indexes{$i} = $under;
100             }
101 24         166 foreach my $index(sort _sorted keys %indexes) {
102 27         738 my $cloned_rules = dclone($rules); # each iteration in the loop needs its own copy of the rules;
103 27 100       113 $cloned_rules = [''] unless @$cloned_rules; # to handle the bare array case
104 27         150 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     144 next if( (ref($value)||'') eq 'HASH') && !%$value;
      100        
107 26         48 push @{$current->{$local_ns}}, $value;
  26         98  
108             }
109             } else {
110 106 100 100     352 if((ref($rules->[0])||'') eq 'ARRAY') {
111 17         83 my $value = $self->_parse_formlike( $context, [@$ns, $rule], shift(@$rules) );
112 17 100       62 next unless %$value; # For 'permitted';
113 10         31 $current->{$rule} = $value;
114             } else {
115 89 100       183 if($rule eq '') {
116 14         40 my $key = join('.', @$ns);
117 14 50       63 unless(defined $context->{$key}) {
118 0 0       0 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>$key) : next;
119             }
120 14         54 $current = $self->_normalize_array_value($context->{$key});
121             } else {
122 75         206 my $key = join('.', @$ns, $rule);
123 75 100       220 unless(defined $context->{$key}) {
124 27 100       951 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>$key) : next;
125             }
126 48         134 $current->{$rule} = $self->_normalize_array_value($context->{$key});
127             }
128             }
129             }
130             }
131 54         124 return $current;
132             }
133              
134             sub _parse_data {
135 38     38   94 my ($self, $context, $ns, $rules) = @_;
136 38         63 my $current = +{};
137 38         69 MAIN: while(@{$rules}) {
  136         306  
138 98         149 my $rule = shift @{$rules};
  98         170  
139 98 100 100     318 if(ref($rule)||'' eq 'HASH') {
140 20         67 my ($local_ns, $rules) = %$rule;
141 20         38 my $value = $context;
142 20         40 foreach my $pointer (@$ns, $local_ns) {
143 30 100       62 if(exists($value->{$pointer})) {
144 20         42 $value = $value->{$pointer};
145             } else {
146 10 50       346 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $local_ns))) : next MAIN;
147             }
148             }
149              
150 10 50 50     41 Catalyst::Exception::InvalidArrayPointer->throw(pointer=>join('.', (@$ns, $local_ns))) unless (ref($value)||'') eq 'ARRAY';
151 10         19 my @gathered = ();
152 10         24 foreach my $item (@$value) {
153 25         542 my $cloned_rules = dclone($rules); # each iteration in the loop needs its own copy of the rules;
154 25 100       100 $cloned_rules = [''] unless @$cloned_rules; # to handle the bare array case
155 25         76 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     118 next if( (ref($value)||'') eq 'HASH') && !%$value;
      100        
158 24         70 push @gathered, $value;
159             }
160 10         37 $current->{$local_ns} = \@gathered;
161             } else {
162 78 100 100     246 if((ref($rules->[0])||'') eq 'ARRAY') {
163 11         55 my $value = $self->_parse_data( $context, [@$ns, $rule], shift(@$rules) );
164 11 100       39 next unless %$value; # For 'permitted';
165 6         16 $current->{$rule} = $value;
166             } else {
167 67 100       134 if($rule eq '') {
168 14         40 my $value = $context;
169 14         34 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         31 $current = $self->_normalize_array_value($value);
177             } else {
178 53         78 my $value = $context;
179 53         136 foreach my $pointer (@$ns, $rule) {
180 91 100 100     348 if(((ref($value)||'') eq 'HASH') && exists($value->{$pointer})) {
      100        
181 74         153 $value = $value->{$pointer};
182             } else {
183 17 50       592 $self->_required ? Catalyst::Exception::MissingParameter->throw(param=>join('.', (@$ns, $rule))) : next MAIN;
184             }
185             }
186 36         82 $current->{$rule} = $self->_normalize_array_value($value);
187             }
188             }
189             }
190             }
191 38         80 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