File Coverage

blib/lib/Plack/Middleware/MangleEnv.pm
Criterion Covered Total %
statement 154 182 84.6
branch 78 104 75.0
condition 13 17 76.4
subroutine 24 26 92.3
pod 17 17 100.0
total 286 346 82.6


line stmt bran cond sub pod time code
1             package Plack::Middleware::MangleEnv;
2              
3 8     8   5045 use strict;
  8         8  
  8         192  
4 8     8   25 use warnings;
  8         9  
  8         231  
5 8     8   28 use Carp qw< confess >;
  8         8  
  8         416  
6 8     8   3442 use English qw< -no_match_vars >;
  8         5269  
  8         30  
7             { our $VERSION = '0.001'; }
8              
9 8     8   2741 use parent 'Plack::Middleware';
  8         237  
  8         49  
10              
11             # Note: manglers in "manglers" here are totally reconstructured and not
12             # necessarily straightly coming from the "mangle" field in the original
13             sub call {
14 6     6 1 90549 my ($self, $env) = @_;
15             VAR:
16 6         10 for my $mangler (@{$self->{_manglers}}) {
  6         25  
17 40         62 my ($key, $value) = @$mangler;
18 40 100 100     208 if ($value->{remove}) {
    100          
    100          
    100          
    100          
    50          
19 5         9 delete $env->{$key};
20             }
21             elsif (exists($env->{$key}) && (!$value->{override})) {
22              
23             # $env->{$key} is already OK here, do nothing!
24             }
25             elsif (exists $value->{value}) { # set unconditionally
26 12         22 $env->{$key} = $value->{value};
27             }
28             elsif (exists $value->{env}) { # copy from other item in $env
29 6         14 $env->{$key} = $env->{$value->{env}};
30             }
31             elsif (exists $value->{ENV}) { # copy from %ENV
32 2         7 $env->{$key} = $ENV{$value->{ENV}};
33             }
34             elsif (exists $value->{sub}) {
35 11         45 $value->{sub}->($env->{$key}, $env, $key);
36             }
37             else {
38 0         0 require Data::Dumper;
39 0         0 my $package = ref $self;
40 0         0 confess "BUG in $package, value for '$key' not as expected: ",
41             Data::Dumper::Dumper($value);
42             } ## end else [ if ($value->{remove}) ]
43             } ## end VAR: for my $mangler (@{$self...})
44              
45 6         54 return $self->app()->($env);
46             } ## end sub call
47              
48             # Initialization code, this is executed once at application startup
49             # so we are more relaxed about *not* calling too many subs
50             sub prepare_app {
51 9     9 1 3008 my ($self) = @_;
52 9         17 $self->_normalize_input_structure(); # reorganize internally
53 9         8 my @inputs = @{$self->{manglers}}; # we will consume @inputs
  9         23  
54 9         36 $self->{_manglers} = [];
55              
56 9         25 while (@inputs) {
57 43         52 my ($key, $value) = splice @inputs, 0, 2;
58 43         80 $self->push_manglers(
59             $self->generate_manglers($key, $value, {override => 1}));
60             }
61              
62 7         29 return $self;
63             } ## end sub prepare_app
64              
65             sub push_manglers {
66 41     41 1 33 my $self = shift;
67 41         27 push @{$self->{_manglers}}, @_;
  41         66  
68 41         99 return $self;
69             } ## end sub push_manglers
70              
71             sub generate_manglers { # simple dispatch method
72 43     43 1 33 my $self = shift;
73 43         35 my ($key, $value) = @_; # ignoring rest of parameters here
74 43         37 my $ref = ref $value;
75 43 100       69 return $self->generate_immediate_manglers(value => @_) unless $ref;
76 36 100       62 return $self->generate_array_manglers(@_) if $ref eq 'ARRAY';
77 30 100       69 return $self->generate_hash_manglers(@_) if $ref eq 'HASH';
78 6 50       17 return $self->generate_code_manglers(@_) if $ref eq 'CODE';
79              
80 0         0 confess "invalid reference '$ref' for '$key'";
81             } ## end sub generate_manglers
82              
83             sub generate_immediate_manglers {
84 41     41 1 57 my ($self, $type, $key, $value, $opts) = @_;
85 41         171 return [$key => {%$opts, $type => $value}];
86             }
87              
88             sub generate_array_manglers {
89 6     6 1 15 my ($self, $key, $aref, $defaults) = @_;
90 6 100       16 return $self->generate_remove_manglers($key, undef, $defaults)
91             if @$aref == 0;
92 3 50       10 return $self->generate_immediate_manglers(value => $key, $aref->[0], $defaults)
93             if @$aref == 1;
94              
95 0         0 my @values = $self->stringified_list(@$aref);
96 0         0 confess "array for '$key' has more than one value (@values)";
97             }
98              
99             sub generate_code_manglers {
100 10     10 1 13 my ($self, $key, $sub, $opts) = @_;
101 10 100       15 $sub = $self->wrap_code($sub)
102             or confess "sub for '$key' is not a CODE reference";
103 8         14 return $self->generate_immediate_manglers(sub => $key, $sub, $opts);
104             }
105              
106             sub generate_hash_manglers {
107 24     24 1 34 my ($self, $key, $hash, $defaults) = @_;
108              
109 24         48 my %opt = %$defaults;
110 24 100       45 $opt{override} = delete($hash->{override}) if exists($hash->{override});
111              
112 24 50       58 if ((my @keys = keys %$hash) > 1) {
113 0         0 @keys = $self->stringified_list(@keys);
114 0         0 confess "too many options ('@keys') for '$key'";
115             }
116              
117 24         54 my ($type, $value) = %$hash;
118 24 50       113 my $cb = $self->can('generate_hash_manglers_' . $type)
119             or confess "unknown option '$type' for '$key'";
120              
121 24         45 return $cb->($self, $key, $value, \%opt);
122             }
123              
124             sub generate_hash_manglers_ENV {
125 4     4 1 5 my $self = shift;
126 4         6 return $self->generate_immediate_manglers(ENV => @_);
127             }
128              
129             sub generate_hash_manglers_env {
130 6     6 1 7 my $self = shift;
131 6         9 return $self->generate_immediate_manglers(env => @_);
132             }
133              
134             sub get_values_from_source {
135 15     15 1 18 my ($self, $env, $source) = @_;
136              
137             # get right start value
138 15         11 my ($type, $sel) = @{$source}{qw< type value >};
  15         23  
139             my $svalue = ($type eq 'env') ? $env->{$sel}
140 15 100       28 : ($type eq 'ENV') ? $ENV{$sel}
    100          
141             : $sel;
142              
143             # flatten if requested and possible
144 15         14 my @values = ($svalue);
145 15 100       22 if ($source->{flatten}) {
146 7 100       12 if (ref($svalue) eq 'ARRAY') {
    50          
147 3         7 @values = @$svalue;
148             }
149             elsif (ref($svalue) eq 'HASH') {
150 0         0 @values = %$svalue;
151             }
152             }
153              
154             # handle undefined values
155 15         13 my $default = $source->{default};
156 15         10 my $doe = $source->{default_on_empty};
157             @values = map {
158 15 100 100     14 (! defined($_)) ? @$default
  27 100       80  
159             : ($doe && (! length($_))) ? @$default
160             : $_;
161             } @values;
162              
163             # filter stuff out
164 15         13 my $remove_if = $source->{remove_if};
165 15 50       12 my @retval = grep { ref($_) || (! $remove_if->{$_}) } @values;
  23         59  
166 15 100       24 return unless @retval;
167              
168 13         28 return @retval;
169             }
170              
171             sub normalize_source {
172 15     15 1 12 my ($self, $source, $defaults) = @_;
173 15         8 my %src;
174 15         14 for my $feature (qw< remove_if default default_on_empty flatten >) {
175             $src{$feature} = exists($source->{$feature})
176 60 100       85 ? delete($source->{$feature}) : $defaults->{$feature};
177             }
178 15         11 $src{remove_if} = { map { $_ => 1 } @{$src{remove_if}} };
  0         0  
  15         16  
179 15 100       32 $src{default} = [$src{default}] unless ref($src{default}) eq 'ARRAY';
180             confess "too many elements in default for list"
181 15 50       8 if @{$src{default}} > 1;
  15         23  
182 15 50       23 confess "too many options in list" if keys(%$source) > 1;
183 15 50       17 confess "nothing to take from in list" if keys(%$source) < 1;
184 15         24 ($src{type}, $src{value}) = %$source;
185             confess "unknown source '$src{type}' in list"
186 15 50       16 unless grep {$_ eq $src{type}} qw< env ENV value >;
  45         47  
187 15         22 return \%src;
188             }
189              
190             sub generate_hash_manglers_list {
191 4     4 1 4 my ($self, $key, $cfg, $opts) = @_;
192 4   50     15 $cfg->{remove_if} ||= [];
193 4   50     15 $cfg->{default} ||= [];
194 4   100     11 $cfg->{default_on_empty} ||= 0;
195 4   100     8 $cfg->{flatten} ||= 0;
196              
197 4         4 my $count = 0;
198 4         15 for my $feature (qw< join sprintf >) {
199 8 100       17 defined(my $v = $cfg->{$feature}) or next;
200 3 50       8 confess "cannot specify both join and sprintf for '$key'"
201             if ++$count > 1;
202 3 100       7 $v = {value => $v} unless ref $v;
203 3         9 $cfg->{$feature} = $self->normalize_source($v, {%$opts, $feature => undef});
204             }
205 4         4 my ($join, $sprintf) = @{$cfg}{qw< join sprintf >};
  4         6  
206              
207             my @sources = map {
208 12         14 $self->normalize_source($_, $cfg);
209 4         3 } @{$cfg->{sources}};
  4         5  
210              
211             my $sub = sub {
212 4     4   7 my ($value, $env, $key) = @_;
213 4         4 my @retval;
214 4         6 for my $source (@sources) {
215 12         19 push @retval, $self->get_values_from_source($env, $source);
216             }
217              
218 4 100       11 if (defined $join) {
    100          
219 2         3 my ($joinstr) = $self->get_values_from_source($env, $join);
220 2         7 $env->{$key} = join $joinstr, @retval;
221             }
222             elsif (defined $sprintf) {
223 1         2 my ($sprintfstr) = $self->get_values_from_source($env, $sprintf);
224 1         8 $env->{$key} = sprintf $sprintfstr, @retval;
225             }
226             else {
227 1         4 $env->{$key} = \@retval;
228             }
229 4         16 };
230 4         6 return $self->generate_immediate_manglers(sub => $key, $sub, $opts);
231             }
232              
233             *generate_hash_manglers_remove = \&generate_remove_manglers;
234             *generate_hash_manglers_sub = \&generate_code_manglers;
235              
236             sub generate_hash_manglers_value {
237 4     4 1 3 my $self = shift;
238 4         6 return $self->generate_immediate_manglers(value => @_);
239             }
240              
241             sub generate_remove_manglers {
242 5     5 1 6 my ($self, $key, $value, $defaults) = @_;
243 5 50 33     18 if ((ref($value) eq 'HASH') && (my @keys = keys(%$value))) {
244 0         0 @keys = $self->stringified_list(@keys);
245 0         0 confess "remove MUST be alone when set to true, found (@keys)";
246             }
247 5         10 return $self->generate_immediate_manglers(remove => $key, 1, {});
248             }
249              
250             sub wrap_code {
251 10     10 1 18 my ($self, $sub) = @_;
252 10 100       54 return unless ref($sub) eq 'CODE';
253             return sub {
254 7 100   7   21 defined(my $retval = $sub->(@_)) or return;
255 6 100       48 $retval = [$retval] unless ref($retval);
256              
257 6         10 my ($value, $env, $key) = @_;
258 6 50       13 confess "sub for '$key' returned an invalid value"
259             unless ref($retval) eq 'ARRAY';
260              
261 6         4 my $n = scalar @$retval;
262 6 50       15 if ($n == 0) {
    50          
263 0         0 delete $env->{$key};
264             }
265             elsif ($n == 1) {
266 6         8 $env->{$key} = $retval->[0];
267             }
268             else {
269 0         0 my @values = $self->stringified_list(@$retval);
270 0         0 confess "too many return values (@values) from sub for '$key'";
271             }
272              
273 6         12 return;
274 8         41 };
275             }
276              
277             sub stringified_list {
278 0     0 1 0 my $self = shift;
279             return map {
280 0 0       0 if (defined(my $v = $_)) {
  0         0  
281 0         0 $v =~ s{([\\'])}{\\$1}gmxs;
282 0         0 "'$v'";
283             }
284             else {
285 0         0 'undef';
286             }
287             } @_;
288             }
289              
290             # _PRIVATE METHODS_
291              
292             sub _normalize_input_structure {
293 9     9   10 my ($self) = @_;
294 9 100       222 if (exists $self->{manglers}) {
295 4         8 local $" = "', '";
296 4         4 my $mangle = $self->{manglers};
297 4 50       13 $mangle = $self->{manglers} = [%$mangle] if ref($mangle) eq 'HASH';
298 4 50       12 confess "'mangle' MUST point to an array or hash reference"
299             unless ref($mangle) eq 'ARRAY';
300 4 50       15 confess "'mangle' array MUST contain an even number of items"
301             if @$mangle % 2;
302 4         11 my @keys = keys %$self;
303             confess "'mangle' MUST be standalone when present (found: '@keys')"
304 4 100       6 if grep { ($_ ne 'app') && ($_ ne 'manglers') } @keys;
  8 50       36  
305             } ## end if (exists $self->{manglers...})
306             else { # anything except app goes into mangle
307 5         8 my $app = delete $self->{app}; # temporarily remove it
308 5         21 %$self = (
309             app => $app, # put it back
310             manglers => [%$self], # with rest as manglers
311             );
312             } ## end else [ if (exists $self->{manglers...})]
313 9         12 return $self;
314             } ## end sub _normalize_input_structure
315              
316             sub _only_one {
317 0     0     my ($self, $hash, @keys) = @_;
318 0           my @found = grep { exists $hash->{$_} } @keys;
  0            
319 0 0         return ($found[0], delete($hash->{$found[0]})) if @found == 1;
320              
321 0           @keys = $self->stringified_list(@keys);
322 0           @found = $self->stringified_list(@found);
323 0 0         confess scalar(@found)
324             ? "one in (@keys) MUST be provided, none found"
325             : "only one in (@keys) is allowed, found (@found)";
326             } ## end sub __exactly_one_key_among
327              
328             1;
329             __END__