File Coverage

blib/lib/Catalyst/Utils/StrongParameters.pm
Criterion Covered Total %
statement 118 127 92.9
branch 49 64 76.5
condition 21 30 70.0
subroutine 14 15 93.3
pod 0 5 0.0
total 202 241 83.8


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