=pod

=encoding utf8

=head1 NAME

Plack::Middleware::MangleEnv - Mangle request environment at will

=head1 VERSION

This document describes Plack::Middleware::MangleEnv version 0.001.

=head1 SYNOPSIS

   use Plack::Middleware::MangleEnv;

   my $mw = Plack::Middleware::MangleEnv->new(

      # overriding is the default behaviour

      # straight value, must be a plain SCALAR (no refs)
      var_name    => 'a simple, overriding value',

      # any value can be wrapped, even refs
      some_value  => [ \@whatever ],

      # or you can be just explicit
      alternative => { value => $whatever },

      # you can read stuff from %ENV
      from_ENV    => { ENV => 'PLACK_ENV' },

      # or read other variables from $env
      from_env    => { env => 'psgi.url_scheme' },

      # turn override into defaulting, works with value, env and ENV
      de_fault    => { value => $something, override => 0 },

      # get rid of a variable, inconditionally. You can pass
      # no value or be explicit about your intent
      delete_pliz => [],
      delete_me   => { remove => 1 },

      # use subroutines for maximum flexibility.
      change_me1  => sub { ... },
      change_me2  => { sub => sub { ... } },

   );

   # you can also pass the key/value pairs as a hash reference
   # associated to a key named 'manglers'. This is necessary e.g. if
   # you want to set a variable in $env with name 'app' (or 'manglers'
   # itself)
   my $mw2 = Plack::Middleware::MangleEnv->new(
      manglers => {
         what => 'EVER',
         who  => 'are you?',
      }
   );

   # when evaluation order or repetition is important... use an array
   # reference for 'manglers'
   my $mw3 = Plack::Middleware::MangleEnv->new(
      manglers => [
         same_as => 'before', # set this at the beginning...
         what => {env => 'same_as'},
         ...,
         same_as => [], # ... and delete this at the end
      ]
   );

=head1 DESCRIPTION

This module allows you to mangle L<Plack>'s C<$env> that is passed along
to the sequence of I<app>s, taking values from:

=over

=item *

direct configuration;

=item *

values from C<%ENV>;

=item *

other values in C<$env> itself;

=item *

subroutines.

=back

=head2 How Variables Are Set

The end goal of this middleware module is to manipulate C<$env> by
adding, changing or deleting its items.

You can pass the different actions to be performed as key-value pairs.
They can either appear directly upon invoking the middleware, as in the
following example:

   my $mw = Plack::Middleware::MangleEnv->new(
      var_name    => 'a simple, overriding value',
      some_value  => [ \@whatever ],
      alternative => { value => $whatever },
      # ... you get the idea
   );

or wrap these pairs inside either an hash or an array reference whose
key is C<manglers>, like in the following examples:

   my $mw_h = Plack::Middleware::MangleEnv->new(
      manglers => {
         var_name    => 'a simple, overriding value',
         some_value  => [ \@whatever ],
         alternative => { value => $whatever },
         # ... you get the idea
      }
   );

   my $mw_a = Plack::Middleware::MangleEnv->new(
      manglers => [
         var_name    => 'a simple, overriding value',
         some_value  => [ \@whatever ],
         alternative => { value => $whatever },
         # ... you get the idea
      ]
   );

Although more verbose, this last approach with an array reference is
important because it allows you to:

=over

=item *

define the exact order of evaluation for mangling actions;

=item *

define multiple actions for the same key, possibly at different stages;

=item *

use keys C<manglers> and C<app>, if you need them.

=back

There's a wide range of possible I<values> that you can set associated
to a key:

=over

=item B<< Simple scalar >>

   key => 'some simple, non-reference scalar',

a I<non-reference> scalar is always taken as-is and then set in C<$env>.

=item B<< Array reference >>

   key => [],
   key => [ { 'a non' => 'trivial scalar' } ],

when you pass an array reference, it can be either empty (in which case
the associated key will be I<removed> from C<$env>) or contain exactly
one value, which will be set into C<$env>.

This alternative allows you to set any scalar, not just non-reference
ones; so the following examples will do what they say:

   # set key to an array ref with numbers 1..3 inside
   key => [ [1..3] ], # note: array ref inside array ref!

   # set key to a hash reference, literally
   key => [ { a => 'b', c => 'd' } ],

   # set key to a sub reference, literally
   key => [ sub { 'I go with key!' } ],

=item B<< Sub reference >>

   key => sub { ... },

the sub reference will be called and its return value used to figure out
the value to associate to the key. See L</Sub Reference Interface> for
details on the expected interface for the sub;

=item B<< Hash reference >>

allows you to be I<verbosely clear> about what you want, in addition to
giving you knobs to modify the behaviour. The allowed keys are the
following:

=over

=item C<env>

points to a string that will be used to extract the value from C<$env>
itself. Useful if you want to I<change the name> of a parameter;

=item C<ENV>

points to a string that will be used to extract the value from C<%ENV>.
Useful if you want to get some variables from the environment.

=item C<list>

points to a hash of configurations to build and handle a list of other
I<sources> (i.e. C<env>, C<ENV> or C<value>).

It collects data from a list of C<sources>, optionally C<flatten>inig
them (if they are array or hash references), filtering them (e.g. to set
default values for C<undef> or empty strings, or removing unwanted
elements), then optionally C<join>inig them or passing them to
C<sprintf>. See L</generate_hash_manglers_list> for the details on this
hash and of each element in C<sources>.

For example, consider the following definition:

   ...
   connect_string => {
      list => {
         default => [], # remove undefined values
         join => ':',   # join with ":"
         sources => [
            {ENV => 'HOST'},
            {ENV => 'PORT'}
         ],
      }
   }
   ...

The following applies:

   %ENV = (HOST => 'localhost'); # leave PORT undefined
   --> $env->{connect_string} = 'localhost';

   %ENV = (HOST => 'localhost', PORT => 80);
   --> $env->{connect_string} = 'localhost:80';

Something similar happens with C<sprintf>, although the format is
stricter in this case and we have to address this with defaults:

   ...
   connect_string => {
      list => {
         default => [], # remove undefined values
         sprintf => '%s:%s',
         sources => [
            {ENV => 'HOST', default => 'www.example.com'},
            {ENV => 'PORT', default => 80},
         ],
      }
   }
   ...

   %ENV = ();
   --> $env->{connect_string} = 'www.example.com:80';

   %ENV = (HOST => 'localhost', PORT => 443);
   --> $env->{connect_string} = 'localhost:443';

In addition to plain strings, you can also set either C<join> or
C<sprintf> (never the two at the same time!) with a I<source>
specification with C<env>, C<ENV> or C<value>:

   ...
   sprintf_template => '%s:%s', # ends up in $env
   connect_string => {
      list => {
         default => [], # remove undefined values
         sprintf => {env => 'sprintf_template'},
         sources => [
            {ENV => 'HOST', default => 'www.example.com'},
            {ENV => 'PORT', default => 80},
         ],
      }
   }
   ...
   # same as before here


=item C<override>

boolean flag that indicates whether the new value overrides a previous
one, if any. Set to a false value to avoid overriding an existing value,
while still being able to provide a default one if the key is missing
from C<$env>.

Defaults to a true value;

=item C<sub>

set a subroutine, see L</Sub Reference Interface> for details;

=item C<value>

set a value that is not a simple plain scalar:

   # set key to an array ref with numbers 1..3 inside
   key => { value => [1..3] },

   # set key to a hash reference, literally
   key => { value => { a => 'b', c => 'd' } },

   # set key to a sub reference, literally
   key => { value => sub { 'I go with key!' } },

=back

Exactly one of the keys C<env>, C<ENV>, C<list>, C<sub> and C<value>
MUST appear in the hash reference. For obvious reasons, you cannot
provide more of them, otherwise a conflict would arise.

=back


=head2 Sub Reference Interface

The most flexible way to mangle C<$env> is through a subroutine. It can
be provided either directly associated to the key, or through the C<sub>
sub-key in the hash associated to the key.

The provided subroutine reference will be called like this:

   sub {
      my ($current_value, $env, $key) = @_;
      # do what you need
      return @something;
   }

The I<sub> can modify C<$env> at will, e.g. by adding new keys or
removing other ones based on your specific logic.

If you don't return anything, or the C<undef> value, the corresponding
C<$key> in C<$env> will be left untouched, keeping its previous value
(if any).  Otherwise, you are supposed to return one single value that
can be:

=over

=item *

B<not> an array reference, in which case it is used as the value
associated to C<$key> in C<$env>;

=item *

an array reference. If the array is empty, the C<$key> is removed from
C<$env>; otherwise, it MUST contain exactly one value, used to set the
key C<$key> in C<$env> (which also allows you to set as output an array
reference, even an empty one).

=back

Examples:

   # nothing happens with this
   sub { return }

   # key is removed from $env
   sub { return [] }

   # key is set to an empty array in $env
   sub { return [[]] }


=head1 METHODS

The following methods are implemented as part of the interface for a
Plack middleware. Although you can override them... there's probably
little sense in doing this!

=over

=item call

=item prepare_app

=back

Methods described in the following subsections can be overridden or used
in derived classes. The various C<generate*_manglers> functions have the
plural form because they can potentially return a list of manglers; in
this module, anyway, each of them returns one single mangler per call.

=head2 B<< generate_array_manglers >>

   my @manglers = $obj->generate_array_manglers($key, $aref, $defaults);

generate manglers starting from an array definition. C<$aref> MUST be
an C<ARRAY> reference. Depending on the number of elements in C<@$aref>:

=over

=item *

if no element is present, a I<remove> mangler is generated via
L</generate_remove_manglers>

=item *

if exactly one element is present, a I<value> mangler is generated via
L</generate_immediate_manglers> with type C<value>

=item *

otherwise, an exception is thrown.

=back

=head2 B<< generate_code_manglers >>

   my @manglers = $obj->generate_code_manglers($key, $sub, $defaults);

generate manglers from a sub definition. C<$sub> MUST be a C<CODE>
reference.

The provided sub is wrapped using L</wrap_code> to set the right
behaviour around C<$sub>, then the output mangler is returned as
follows:

   [$key => {%$defaults, wrapsub => $wrapped_sub}]

=head2 B<< generate_hash_manglers >>

   my @manglers = $obj->generate_hash_manglers($key, $hash, $defaults);

generate manglers from a hash definition. C<$hash> MUST be a C<HASH>
reference. This is actually a dispatcher for the different methods named
C<generate_hash_manglers_*>, which can be overridden or added in derived
classes to support further conversion types.

=head2 B<< generate_hash_manglers_ENV >>

   my @manglers = $obj->generate_hash_manglers_ENV($key, $name, $opts);

generate mangler for taking C<$ENV{$name}>.

=head2 B<< generate_hash_manglers_env >>

   my @manglers = $obj->generate_hash_manglers_env($key, $name, $opts);

generate mangler for taking C<$env->{$name}>.

=head2 B<< generate_hash_manglers_list >>

   my @manglers = $obj->generate_hash_manglers_env($key, $cfg, $opts);

generate mangler for generating a list of elements from hash
configuration C<%$cfg>. The following keys are supported:

=over

=item C<default>

a default value to assign if the source element is not present or has an
undefined value. If it is an array reference, it follows the same rules
explained at L</Array reference>. If an item in C<sources> has a
C<default> configuration, that takes precedence. If not present, the
same as setting C<[]> is assumed (i.e. undefined values will be
removed).

=item C<default_on_empty>

boolean, apply the C<default> above to empty values too, in addition to
undefined values. Defaults to 0;

=item C<flatten>

boolean, turn array reference values into a list. Defaults to 0;

=item C<join>

specification of a I<source> (see below for the I<source specification>
provided for key C<sources>) where it's possible to take a string for
joining the elements of the list together. In this case, a single string
will be returned.

=item C<remove_if>

array reference to strings that will be removed when found;

=item C<sources>

array reference of hashes, explained below.

=back

The different source specifications in C<sources> can take all the keys
above as an overriding specialization (with the exception of
C<sources>), and the following additional ones:

=over

=item C<env>

=item C<ENV>

=item C<value>

mutually exclusive keys indicating where the value should be taken from.

=back

=head2 B<< generate_hash_manglers_remove >>

Same as L</generate_remove_manglers>.

=head2 B<< generate_hash_manglers_sub >>

Same as L</generate_code_manglers>.

=head2 B<< generate_hash_manglers_value >>

   my @manglers =
     $obj->generate_hash_manglers_value($key, $value, $opts);

generate mangler for taking C<$value>.

=head2 B<< generate_immediate_manglers >>

   my @manglers =
     $obj->generate_immediate_manglers($type, $key, $value, $opts);

generates this mangler:

   [$key => {%$opts, $type => $value}]

i.e. the standard mangler for setting something immediately handled.

Note that this has the initial value for C<$type>, differently from
other C<generate*_manglers> functions.

=head2 B<< generate_manglers >>

   my @manglers = $obj->generate_manglers($key, $value, $defaults);

Generate zero, one or more manglers, dispatching to the proper function
depending on the type of C<$value>. C<$defaults> is a hash reference
holding default values for the generated mangler, e.g. setting
C<override> to 1 by default.

At the moment, all generation methods return exactly one mangler per
call. This can of course change in derived classes, hence the returned
value can contain any number of items.

This method does the following dispatching based on C<ref($value)>:

=over

=item *

non-reference scalars: L</generate_immediate_manglers> with type C<value>

=item *

array references: L</generate_array_manglers>

=item *

hash references: L</generate_hash_manglers>

=item *

code references: L</generate_code_manglers>

=item *

anything else throws an exception.

=back

If you want to override it (e.g. to add support for different types, or
change the default ones described above) you might augment it like in
the following example:

   # suppose we want to do something with Regexp references

   package Plack::Middleware::MangleEnv::Derived;
   use parent 'Plack::Middleware::MangleEnv';
   sub generate_manglers {
      my $self = shift;

      return $self->generate_regex_manglers(@_)
        if ref($_[1]) eq 'Regexp';

      return $self->SUPER::generate_manglers(@_);
   }
   sub generate_regex_manglers {
      my ($self, $key, $regex, $defaults) = @_;
      my $sub = sub {
         defined(my $value = shift) or return; # do nothing if undef
         my ($capture) = $value =~ m{$regex};
         return $capture;
      };
      my $wrapsub = $self->wrap_code($sub);
      return [$key => {%$defaults, wrapsub => $wrapsub}];
   }
   1;

=head2 B<< generate_remove_manglers >>

   my @manglers =
     $obj->generate_remove_manglers($key, $value, $defaults);

convenience function to generate manglers for removing. Such manglers
are supposed to have this form:

   [ $key => { remove => 1 } ]

and this function does exactly this, ignoring C<$defaults> and checking
that C<$value> is empty if it is a hash reference (it is used by
L</generate_hash_manglers> behind the scenes, so this checks that there
are no further keys in the input mangler definition).

=head2 B<< get_values_from_source >>

   my @values = $obj->get_values_from_source($env, $spec);

runtime helper that expands the C<$spec> according to the rules
explained in L</generate_hash_manglers_list> for each source.

=head2 B<< normalize_source >>

   my $normal = $obj->normalize_source($source, $defaults);

normalizes a source (see L</generate_hash_manglers_list>) turning the
pair with key C<env>, C<ENV> or C<value> into two pair, one with key
C<type> and another one with key C<value>. Example:

   { env => 'whatever' }  -->  { type => 'env', value => 'whatever' }

Values for options (e.g. C<default>, C<default_on_empty>, etc.) are
taken from hash reference C<$source> and, if absent, from C<$defaults>.

=head2 B<< push_manglers >>

   $obj->push_manglers->(@manglers);

add the provided C<@manglers> to the list of manglers that will be used
at runtime.

Used by C<prepare_app> to populate the list of runtime manglers from the
provided inputs. For every input definition of a mangler,
L</generate_manglers> is called and its output fed to this method, like
this:

   my @manglers = $obj->generate_manglers(...);
   $obj->push_manglers(@manglers);

You might want to override this method if you want to further process
I<all> the generated manglers, like this:

   package Plack::Middleware::MangleEnv::Derived;
   use parent 'Plack::Middleware::MangleEnv';
   sub push_manglers {
      my $self = shift;
      my @manglers = map { do_something($_) } @_;
      return $self->SUPER::push_manglers(@manglers);
   }
   ...


=head2 B<< stringified_list >>

   my @strings = $obj->stringified_list(@list);

convenience function to generate a list of strings suitable for logging.
Defined element are escaped and put into single quotes, while C<undef>
is rendered as the string C<undef> (without quoting).

=head2 B<< wrap_code >>

   my $wrapped_sub = $obj->wrap_code($sub);

wrap a code sub adhering to the L</Sub Reference Interface> to implement
the behaviour described in the same subsection. It is used by both
L</generate_code_manglers> and L</generate_hash_manglers> to wrap input
C<sub>s.


=head1 BUGS AND LIMITATIONS

Report bugs either through RT or GitHub (patches welcome).

=head1 SEE ALSO

L<Plack>, L<Plack::Middleware::ForceEnv>,
L<Plack::Middleware::ReverseProxy>,
L<Plack::Middleware::SetEnvFromHeader>,
L<Plack::Middleware::SetLocalEnv>.

=head1 AUTHOR

Flavio Poletti <polettix@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2016 by Flavio Poletti <polettix@cpan.org>

This module is free software. You can redistribute it and/or modify it
under the terms of the Artistic License 2.0.

This program is distributed in the hope that it will be useful, but
without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.

=cut
